# HG changeset patch # User tboudreau@netbeans.org # Date 1269624429 14400 # Branch menus-rewrite-182698 # Node ID 1f7b9142066385d53b1e87b246fd40f591bfebae # Parent e60cab7ba0549eebc6a3cb913ecf4f2dcc29bae3 Initial branch # HG changeset patch # User tboudreau@netbeans.org # Date 1269678750 14400 # Branch menus-rewrite-182698 # Node ID a60721f477e2c24c94a57db36e4563a2c870cc7d # Parent 1f7b9142066385d53b1e87b246fd40f591bfebae #182698 - simplified rewrite of main menu infrastructure. Eliminate use of MenuBar and FolderInstance. diff -r 1f7b91420663 -r a60721f477e2 api.menus/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/build.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,5 @@ + + + Builds, tests, and runs the project org.netbeans.api.menus + + diff -r 1f7b91420663 -r a60721f477e2 api.menus/manifest.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/manifest.mf Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: false +OpenIDE-Module: org.netbeans.api.menus/1 +OpenIDE-Module-Localizing-Bundle: org/netbeans/api/menus/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 +OpenIDE-Module-Recommends: org.netbeans.api.menus.MenuProvider + diff -r 1f7b91420663 -r a60721f477e2 api.menus/nbproject/project.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/nbproject/project.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,4 @@ +is.autoload=true +javac.source=1.6 +javac.compilerargs=-Xlint -Xlint:-serial +nbm.module.author=Tim Boudreau diff -r 1f7b91420663 -r a60721f477e2 api.menus/nbproject/project.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,30 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.api.menus + + + org.openide.util + + + + 8.2 + + + + org.openide.util.lookup + + + + 8.2 + + + + + org.netbeans.api.menus + + + + diff -r 1f7b91420663 -r a60721f477e2 api.menus/src/org/netbeans/api/menus/Bundle.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/src/org/netbeans/api/menus/Bundle.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +OpenIDE-Module-Display-Category=Infrastructure +OpenIDE-Module-Long-Description=\ + API which allows another module to inject a factory which creates the main window's menu bar. +OpenIDE-Module-Name=Main Menu API +FILE_MENU=File +EXIT_MENU_ITEM=Exit +OpenIDE-Module-Short-Description=API for generating the application's menu(s) diff -r 1f7b91420663 -r a60721f477e2 api.menus/src/org/netbeans/api/menus/MenuProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/src/org/netbeans/api/menus/MenuProvider.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,142 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.api.menus; + +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import org.openide.LifecycleManager; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.Parameters; + +/** + * Factory for the application window main menu. Registered in the default + * lookup. + * + * @author Tim Boudreau + * @since 1.0 + */ +public abstract class MenuProvider { + + /** + * Install a menu in a JFrame. Called by the window system to set up the + * main menu. This method is not guaranteed to return the same JMenuBar + * if called repeatedly, and should not be used as a way to look up the + * main window's menu. + *

+ * This method must be called on the AWT event dispatch thread. + *

+ * If no MenuProvider is registered in the default lookup, will create a + * menu which simply contains a File menu with an Exit menu item that + * uses LifecycleManager to cleanly exit the application. + * + * @param frame A JFrame. May not be null. + * @return The JMenuBar which has been installed in the frame. + */ + public static JMenuBar installMenu(JFrame frame) { + Parameters.notNull("frame", frame); //NOI18N + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); //NOI18N + } + MenuProvider p = Lookup.getDefault().lookup(MenuProvider.class); + JMenuBar menu = p == null ? fallbackMenu() : p.createMenu(frame); + if (p == null) { + Logger.getLogger(MenuProvider.class.getName()).log (Level.WARNING, + "No instance of {0} registered in the default lookup. " + //NOI18N + "Using fallback.", MenuProvider.class.getName()); //NOI18N + } + if (menu == null) { + if (p != null) { + Exceptions.printStackTrace(new NullPointerException(p + + " returned null menu")); //NOI18N + } + menu = fallbackMenu(); + } + frame.setJMenuBar(menu); + return menu; + } + + private static JMenuBar fallbackMenu() { + String fileMenuName = NbBundle.getMessage(MenuProvider.class, + "FILE_MENU"); //NOI18N + String exitItemName = NbBundle.getMessage(MenuProvider.class, + "EXIT_MENU_ITEM"); //NOI18N + JMenuBar result = new JMenuBar(); + JMenu fileMenu = new JMenu(fileMenuName); + Action exitAction = new ExitAction(); + exitAction.putValue(Action.NAME, exitItemName); + fileMenu.add(exitAction); + result.add(fileMenu); + return result; + } + + /** + * Create a menu for the provided JFrame. This method is not expected + * to cache the return value. It should not actually call the target frame's + * setJMenuBar() method - the target frame is provided as a + * convenience in case some multi-frame application provides menus for + * multiple frames. + *

+ * This method will only be called by NetBeans from the event dispatch thread. + * Constructing components is safe here, but it is preferable not to perform + * heavy-duty I/O in this method, but rather to create the initial menu bar + * quickly, and then populate its menus lazily or in the background. + *

+ * @param target The frame the menu should be created for + * @since 1.0 + * @return A JMenuBar + */ + protected abstract JMenuBar createMenu(JFrame target); + + private static final class ExitAction extends AbstractAction { + + @Override + public void actionPerformed(ActionEvent e) { + LifecycleManager.getDefault().exit(); + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/build.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,5 @@ + + + Builds, tests, and runs the project org.netbeans.core.menu + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/manifest.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/manifest.mf Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.netbeans.core.menu +OpenIDE-Module-Layer: org/netbeans/core/menu/layer.xml +OpenIDE-Module-Localizing-Bundle: org/netbeans/core/menu/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 +OpenIDE-Module-Provides: org.netbeans.api.menus.MenuProvider + diff -r 1f7b91420663 -r a60721f477e2 core.menu/nbproject/project.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/nbproject/project.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,2 @@ +javac.source=1.6 +javac.compilerargs=-Xlint -Xlint:-serial diff -r 1f7b91420663 -r a60721f477e2 core.menu/nbproject/project.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,83 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.core.menu + + + org.netbeans.api.menus + + + + 1 + 1.0 + + + + org.openide.awt + + + + 7.21 + + + + org.openide.filesystems + + + + 7.37 + + + + org.openide.loaders + + + + 7.13 + + + + org.openide.nodes + + + + 7.14 + + + + org.openide.util + + + + 8.2 + + + + org.openide.util.lookup + + + + 8.2 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ActionModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ActionModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,125 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import javax.swing.Action; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenuItem; +import org.openide.awt.AcceleratorBinding; +import org.openide.awt.Actions; +import org.openide.awt.Mnemonics; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.actions.BooleanStateAction; +import org.openide.util.actions.Presenter; +import org.openide.util.actions.SystemAction; + +/** + * + * @author Tim Boudreau + */ +final class ActionModelEntry extends ThreadUnconstrainedModelEntry { + ActionModelEntry(DataObject dob) { + super (dob, JMenuItem.class, Action.class); + } + + @Override + protected JMenuItem refreshComponent(JMenuItem old) { + Action a; + try { + a = get(); + if (old.getAction() == a) { + configureName (a, old); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return old; + } + + private String getActionName(Action action) { + String name = (String) action.getValue(Action.NAME); + if (name == null && action instanceof SystemAction) { + name = ((SystemAction) action).getName(); + } + name = name == null ? getName() : name; + return name; + } + + private void configureName (Action action, JMenuItem item) { + String txt = item.getText(); + if (txt == null || txt.length() == 0) { + Mnemonics.setLocalizedText(item, getActionName(action)); + item.setToolTipText(null); + } + } + + @Override + protected JMenuItem findComponent() { + try { + Action action = get(); + if (action instanceof Presenter.Menu) { + throw new IllegalStateException("ActionModelEntry used for a menu presenter action"); + } + JMenuItem item; + if (action instanceof BooleanStateAction) { + BooleanStateAction bse = (BooleanStateAction) action; + JCheckBoxMenuItem cbItem = new JCheckBoxMenuItem(); + configureName (action, cbItem); + Actions.connect(cbItem, bse, false); + item = cbItem; + } else { + item = new JMenuItem(); + configureName (action, item); + Actions.connect (item, action); + } + AcceleratorBinding.setAccelerator(action, file().getPrimaryFile()); + return item; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JMenuItem("Error from " + file().getName()); //XXX + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/Bundle.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/Bundle.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,1 @@ +OpenIDE-Module-Name=Core Main Menu diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/Callback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/Callback.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +/** + * + * @author Tim Boudreau + */ +interface Callback { + + void onShow(ModelEntry entry); + + void onHide(ModelEntry entry); +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ComponentList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ComponentList.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,178 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import javax.swing.JComponent; +import javax.swing.JSeparator; +import org.openide.util.Parameters; + +/** + * Wrapper for an array of components which can be checked for value equality. + * + * @author Tim Boudreau + */ +final class ComponentList { + private final JComponent[] comps; + private final Reference[] refs; + private volatile boolean isWeak; + ComponentList(JComponent[] comps) { + Parameters.notNull("comps", comps); + this.comps = new JComponent[comps.length]; + refs = new Reference[comps.length]; + for (int i= 0; i < comps.length; i++) { + if (comps[i] == null) { + this.comps[i] = new JSeparator(); + } else { + this.comps[i] = comps[i]; + } + refs[i] = new WeakReference(this.comps[i]); + } + } + + JComponent get (int ix) { + JComponent result = comps[ix]; + if (comps[ix] == null) { + result = refs[ix] == null ? null : (JComponent) refs[ix].get(); + } + return result; + } + + private void set (int ix, JComponent comp) { + Parameters.notNull("comp", comp); //NOI18N + comps[ix] = comp; + refs[ix] = new WeakReference(comps[ix]); + } + + int size() { + return comps.length; + } + + public JComponent[] getComponents() { + JComponent[] result = new JComponent[size()]; + for (int i= 0; i < result.length; i++) { + result[i] = get(i); + } + return result; + } + + boolean addNotify() { + boolean result = false; + if (!result) { + result = true; + for (int i= 0; i < comps.length; i++) { + if (comps[i] == null) { + comps[i] = refs[i] == null ? null : (JComponent) refs[i].get(); + if (comps[i] == null) { + result = false; + } + } + } + } + isWeak = result; + return result; + } + + void removeNotify() { +// if (!isWeak) { + for (int i = 0; i < comps.length; i++) { + comps[i] = null; + } +// } + isWeak = true; + } + + boolean repair (JComponent[] nue) { + Parameters.notNull("nue", nue); + if (nue.length != comps.length) { + return false; + } + boolean result = true; + for (int i= 0; i < comps.length; i++) { + JComponent c = get(i); + if (c == null || c == nue[i]) { + if (nue[i] == null) { + throw new NullPointerException ("Null element in component " + //NOI18N + "list at " + i); //NOI18N + } + set (i, nue[i]); + } else { + result = false; + } + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != ComponentList.class) { + return false; + } + final ComponentList other = (ComponentList) obj; + if (other == this) { + return true; + } + if (other.comps == comps) { + return true; + } + if (other.comps.length != comps.length) { + return false; + } + for (int i=0; i < comps.length; i++) { + //test only on exact equality + JComponent a = get(i); + JComponent b = other.get(i); + if (a != b) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + Arrays.deepHashCode(this.comps); + return hash; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,82 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.io.IOException; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +final class ComponentModelEntry extends FileModelEntry { + ComponentModelEntry (DataObject dob) { + super (dob, JComponent.class, JComponent.class); + } + + @Override + protected boolean initBackground(List> children) { + return false; + } + + @Override + protected JComponent findComponent() { + try { + return get(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JMenuItem("Error getting comp for " + getName()); + } + + @Override + protected JComponent refreshComponent(JComponent old) { + return old; + } + + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,85 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +public class DynamicModelEntry extends ThreadUnconstrainedModelEntry { + DynamicModelEntry(DataObject dob) { + super (dob, JComponent.class, DynamicMenuContent.class); + } + + @Override + protected JComponent[] findComponents() { + try { + DynamicMenuContent dyn = get(); + JComponent[] result = dyn.getMenuPresenters(); + //Necessary the first time, or we get bogus elements + dyn.synchMenuPresenters(result); + return result; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JComponent[] { new JMenuItem("Error getting " + file().getName()) }; //XXX + } + + @Override + protected JComponent[] refresh(JComponent[] old) { + try { + return get().synchMenuPresenters(old); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return old; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/FileModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/FileModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,130 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.EventQueue; +import java.io.IOException; +import javax.swing.JComponent; +import org.openide.cookies.InstanceCookie; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; + +/** + * + * @author Tim Boudreau + */ +abstract class FileModelEntry extends ModelEntry { + protected final Class modelObjectType; + FileModelEntry(DataObject dob, Class componentType, Class modelObjectType) { + super (dob, DataObject.class, componentType); + this.modelObjectType = modelObjectType; + assert !(dob instanceof DataFolder) : "Folder used for menu item"; //NOI18N + } + + private final InstanceCookie cookie() { + DataObject dob = file(); + InstanceCookie ck = dob.getLookup().lookup(InstanceCookie.class); + if (ck == null) { + ck = new EmptyInstanceCookie(dob.getName()); + } + return ck; + } + + final boolean requiresEq() { + return Component.class.isAssignableFrom(dobType); + } + + protected T get() throws IOException, ClassNotFoundException { + if (requiresEq() && !EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); + } + return modelObjectType.cast(cookie().instanceCreate()); + } + + private static final class EmptyInstanceCookie implements InstanceCookie, InstanceCookie.Of { + private final String name; + EmptyInstanceCookie (String name) { + this.name = name; + } + + @Override + public String instanceName() { + return name; + } + + @Override + public Class instanceClass() throws IOException, ClassNotFoundException { + return Object.class; + } + + @Override + public Object instanceCreate() throws IOException, ClassNotFoundException { + return null; + } + + @Override + public boolean instanceOf(Class type) { + return false; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EmptyInstanceCookie other = (EmptyInstanceCookie) obj; + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,336 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.EventQueue; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JSeparator; +import org.openide.awt.DynamicMenuContent; +import org.openide.cookies.InstanceCookie; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.Parameters; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +abstract class FolderModelEntry extends ModelEntry implements Callback { + + private final List> children = new ArrayList>(15); + private final Object childLock = new Object(); + private final Object mapLock = new Object(); + private final Map, ComponentList> comps4model = + new HashMap, ComponentList>(); + + FolderModelEntry(DataFolder fld, Class compType) { + super(fld, DataFolder.class, compType); + } + + boolean setChildren(List> l) { + boolean result; + synchronized (childLock) { + result = !children.equals(l); + if (result) { + children.clear(); + children.addAll(l); + } + } + synchronized (mapLock) { + if (!comps4model.isEmpty()) { + Set> known = new HashSet>(comps4model.keySet()); + known.removeAll(l); + for (ModelEntry m : known) { + comps4model.remove(m); + } + } + } + return result; + } + + @Override + public final void onHide(ModelEntry entry) { + synchronized (mapLock) { + for (Map.Entry, ComponentList> e : comps4model.entrySet()) { + e.getValue().removeNotify(); + } + } + } + + @Override + public final void onShow(ModelEntry entry) { + synchronized (mapLock) { + for (Map.Entry, ComponentList> e : comps4model.entrySet()) { + e.getValue().addNotify(); + } + } + } + + private ComponentList components(ModelEntry e) { + Parameters.notNull("e", e); + synchronized (mapLock) { + return comps4model.get(e); + } + } + + private void setComponents (ModelEntry e, ComponentList comps) { + Parameters.notNull("comps", comps); + Parameters.notNull("e", e); + synchronized (mapLock) { + if (comps == null) { + comps4model.remove(e); + } else { + comps4model.put(e, comps); + } + } + } + + List> children() { + synchronized (childLock) { + return new ArrayList>(children); + } + } + + protected void onInitBackground(List> children) { + //do nothing - for subclasses to force init + } + + @Override + protected boolean initBackground(List> children) { +// System.err.println("initBackground " + getName()); + DataFolder fld = file(); + boolean changed = true; + Set> created = new HashSet>(); + try { + DataObject[] ch = fld.getChildren(); + if (ch != null) { //can be null + outer: + for (DataObject dob : ch) { + try { + for (Types t : Types.values()) { + if (t.accept(dob)) { + ModelEntry e = t.createEntry(dob, this); + created.add(e); + children.add(e); + //XXX defer or batch afterward? + List> kids = new ArrayList>(); + boolean hasKids = e.initBackground(kids); + changed |= hasKids; + if (hasKids) { + if (e instanceof FolderModelEntry) { + FolderModelEntry fme = FolderModelEntry.class.cast(e); + changed |= fme.setChildren(kids); + } + } + continue outer; + } + } +// System.err.println("No type accepted " + dob.getPrimaryFile().getPath() + " : " + ic(dob)); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + } + } + } finally { + onInitBackground(children); + } + return changed; + } + +// private static String ic(DataObject dob) throws ClassNotFoundException, IOException { +// InstanceCookie ck = dob.getCookie(InstanceCookie.class); +// return ck == null ? " [no ic]" : ck.instanceClass().getName(); +// } + @Override + protected CompType refreshComponent(CompType old) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); + } +// long start = System.currentTimeMillis(); + Parameters.notNull("old", old); + old.removeAll(); + List all = new LinkedList(); + List> kids = children(); + Boolean expectHasIcon = false; + for (ModelEntry e : kids) { + ComponentList c = components(e); + JComponent[] comps; + if (c == null) { + comps = e.getComponents(); + if (comps != null) { + all.addAll(Arrays.asList(comps)); + setComponents(e, c = new ComponentList(comps)); + } + } else { + boolean allStillAlive = c.addNotify(); + if (allStillAlive) { + comps = e.refresh(c.getComponents()); + ComponentList newComps; + if (comps != null && !((newComps = new ComponentList(comps)).equals(c))) { + setComponents(e, newComps); + c = newComps; + } + } else { + comps = e.findComponents(); + boolean fixed = c.repair(comps); + if (!fixed) { + setComponents(e, c = new ComponentList(comps)); + } + } + } + if (comps != null) { + for (JComponent comp : comps) { + expectHasIcon = ModelEntry.defaultHasIcon(comp, expectHasIcon); + } + } + if (c != null) { + all.addAll(Arrays.asList(c.getComponents())); + } + } + pruneSeparatorsAndFixIcons(all, expectHasIcon); + installComponents(all, old); +// long dur = System.currentTimeMillis() - start; +// System.err.println("Refresh " + getName() + " took " + dur + " milliseconds"); + return old; + } + + protected void installComponents(List components, CompType into) { + for (Component c : components) { + if (c == null) { + c = new JSeparator(); + } + into.add(c); + } + } + + private void pruneSeparatorsAndFixIcons(List all, Boolean icons) { + boolean lastWasSep = true; + for (Iterator i = all.iterator(); i.hasNext();) { + JComponent c = i.next(); + boolean isSep = c instanceof JSeparator; + if ((lastWasSep && isSep) || (!i.hasNext() && isSep)) { + i.remove(); + } + lastWasSep = isSep; + if (icons != null) { + ModelEntry.configureIcon(c, icons); + } + } + } + + private static enum Types { + + SEPARATOR(JSeparator.class), + DYNAMIC_MENU_CONTENT(DynamicMenuContent.class), + PRESENTER(Presenter.Menu.class), + MENU, + COMPONENT(Component.class), + ACTION(Action.class); + private final Class type; + + Types(Class type) { + this.type = type; + } + + Types() { + this(Object.class); + } + + boolean accept(DataObject dob) throws IOException, ClassNotFoundException { + if (this == MENU) { + DataFolder fld = dob instanceof DataFolder ? (DataFolder) dob + : dob.getLookup().lookup(DataFolder.class); + return fld != null; + } + InstanceCookie ck = dob.getLookup().lookup(InstanceCookie.class); + return ck instanceof InstanceCookie.Of ? ((InstanceCookie.Of) ck).instanceOf(type) + : ck == null ? false : type.isAssignableFrom(ck.instanceClass()); + } + + ModelEntry createEntry(DataObject ob, Callback callback) { +// System.err.println("Create " + name() + " entry for " + ob.getPrimaryFile().getPath()); + switch (this) { + case SEPARATOR: + return new SeparatorModelEntry(ob); + case ACTION: + InstanceCookie ic = ob.getLookup().lookup(InstanceCookie.class); + try { + //This is a bit of a mess - some InstanceCookies lie + if (ic.instanceCreate() instanceof Presenter.Menu) { + return new PresenterEntry(ob); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new ActionModelEntry(ob); + case DYNAMIC_MENU_CONTENT: + return new DynamicModelEntry(ob); + case PRESENTER: + return new PresenterEntry(ob); + case MENU: + DataFolder fld = ob instanceof DataFolder ? (DataFolder) ob + : ob.getLookup().lookup(DataFolder.class); + Parameters.notNull("folder was non-null but is now null", fld); + return new MenuEntry(fld, null); + case COMPONENT: + return new ComponentModelEntry(ob); + default: + throw new AssertionError(this + ""); + } + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.util.LinkedList; +import java.util.List; +import javax.swing.JMenuBar; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileRenameEvent; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +final class MenuBarModel extends FolderModelEntry { + private final JMenuBar bar; + private FCA fca = new FCA(); + private final RequestProcessor.Task task; + private static final int DELAY = 100; + MenuBarModel (DataFolder rootFolder, JMenuBar bar, RequestProcessor rp) { + super (rootFolder, JMenuBar.class); + this.bar = bar; + this.task = rp.create(fca); + rootFolder.getPrimaryFile().addFileChangeListener(fca); + } + + void init() { + fca.refresh(); + } + + @Override + protected JMenuBar findComponent() { + return bar; + } + + @Override + protected void onInitBackground(List> children) { + super.onInitBackground(children); + if (!refreshing) { + EventQueue.invokeLater(fca); + } + } + + volatile boolean refreshing; + private final class FCA extends FileChangeAdapter implements Runnable { + private volatile boolean enqueued; + boolean refresh() { + boolean result; + synchronized (this) { + result = enqueued; + if (!enqueued) { + enqueued = true; + task.schedule(DELAY); + } + return result; + } + } + + @Override + public void run() { + if (!EventQueue.isDispatchThread()) { + refreshing = true; + try { + List> l = new LinkedList>(); + initBackground(l); + setChildren(l); + } finally { + refreshing = false; + EventQueue.invokeLater(this); + } + } else { + enqueued = false; + bar.removeAll(); + MenuBarModel.this.refresh(new JMenuBar[] { bar }); + } + } + + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + refresh(); + } + + @Override + public void fileChanged(FileEvent fe) { + refresh(); + } + + @Override + public void fileDataCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileDeleted(FileEvent fe) { + refresh(); + } + + @Override + public void fileFolderCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileRenamed(FileRenameEvent fe) { + refresh(); + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,106 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import javax.swing.ButtonModel; +import javax.swing.JMenu; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.awt.Mnemonics; +import org.openide.loaders.DataFolder; + +/** + * + * @author Tim Boudreau + */ +final class MenuEntry extends FolderModelEntry implements ChangeListener { + + private Reference menu; + private final Callback callback; + + MenuEntry (DataFolder fld, Callback callback) { + super (fld, JMenu.class); + this.callback = callback == null ? this : callback; + } + + private JMenu menu() { + JMenu result = menu == null ? null : menu.get(); + if (result == null) { + result = new JMenu(); + Mnemonics.setLocalizedText(result, getName()); + result.getModel().addChangeListener(this); + menu = new WeakReference (result); + } + return result; + } + + @Override + protected JMenu findComponent() { + assert EventQueue.isDispatchThread(); + return menu(); + } + + private boolean wasSelected = false; + @Override + public void stateChanged(ChangeEvent e) { + ButtonModel mdl = (ButtonModel) e.getSource(); + boolean selected = mdl.isSelected(); + //XXX wasSelected could be true if the old component was garbage collected + //without resetting its model - possible? + if (selected && !wasSelected) { + if (callback != null) { + callback.onShow(this); + } + refreshComponent(menu()); + } else if (wasSelected && !selected) { + JMenu m = menu == null ? null : menu.get(); + if (m != null) { + m.removeAll(); + } + if (callback != null) { + callback.onHide(this); + } + } + wasSelected = selected; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,63 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import javax.swing.JMenuBar; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +final class MenuManager { + private final JMenuBar bar; + private final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); + private final MenuBarModel model; + + MenuManager(DataFolder folder, JMenuBar bar) { + this.bar = bar; + model = new MenuBarModel (folder, bar, rp); + } + + void init() { + model.init(); + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import javax.swing.JFrame; +import javax.swing.JMenuBar; +import org.netbeans.api.menus.MenuProvider; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataFolder; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Tim Boudreau + */ +@ServiceProvider(service=MenuProvider.class) +public final class MenuProviderImpl extends MenuProvider { + private static final String MENU_FOLDER = "Menu"; //NOI18N + private FileObject menuFolder; + public MenuProviderImpl () { + this (FileUtil.getConfigFile(MENU_FOLDER)); + } + + MenuProviderImpl (FileObject menuFolder) { //used in unit tests w/ memfs + this.menuFolder = menuFolder; + } + + @Override + protected JMenuBar createMenu(JFrame target) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); //NOI18N + } + FileObject menuFO = menuFolder; + DataFolder menuFolder = DataFolder.findFolder(menuFO); + JMenuBar bar = new JMenuBar(); + bar.setBorderPainted(false); + MenuManager mgr = new MenuManager(menuFolder, bar); + mgr.init(); + return bar; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,172 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.lang.reflect.Array; +import java.util.List; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.loaders.DataObject; +import org.openide.util.ImageUtilities; +import org.openide.util.Parameters; + +/** + * + * @author Tim Boudreau + */ +public abstract class ModelEntry { + private final DataObjectType file; + protected final Class dobType; + private Class compType; + protected static final Icon BLANK_ICON = ImageUtilities.loadImageIcon("org/openide/loaders/empty.gif", false); // NOI18N + public ModelEntry(DataObjectType file, Class dobType, Class compType) { + this.file = file; + this.dobType = dobType; + this.compType = compType; + Parameters.notNull("compType", compType); + Parameters.notNull("dobType", dobType); + Parameters.notNull("file", file); + } + + protected abstract boolean initBackground(List> children); + + public String getName() { + return file().getNodeDelegate().getDisplayName(); + } + + protected final DataObjectType file() { + return file; + } + + final Class componentType() { + return compType; + } + + final CompType[] getComponents() { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); //NOI18N + } + return findComponents(); + } + + final CompType[] refreshComponents(CompType[] old) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); //NOI18N + } + return refresh (old); + } + + protected CompType[] refresh (CompType[] old) { + return toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + } + + protected CompType[] findComponents() { + return toCompTypeArray (new Object[] { findComponent() }); + } + + protected final CompType[] toCompTypeArray(CompType type) { + return toCompTypeArray (new Object[] { type }); + } + + protected final CompType[] toCompTypeArray(Object[] objs) { + CompType[] c = (CompType[]) Array.newInstance(compType, objs == null ? 0 : objs.length); + System.arraycopy(objs, 0, c, 0, objs.length); + return c; + } + + protected CompType findComponent() { + throw new UnsupportedOperationException("Override findComponent() " + //NOI18N + "or findComponents() in " + getClass().getName()); //NOI18N + } + + protected CompType refreshComponent(CompType old) { + throw new UnsupportedOperationException("Override refreshComponent() or " + //NOI18N + "refreshComponents() in " + getClass().getName()); //NOI18N + } + + protected static void configureIcon (JComponent comp, boolean wantIcons) { + if (comp instanceof JMenuItem) { + JMenuItem item = (JMenuItem) comp; + Icon icon = item.getIcon(); + if (!wantIcons && icon != null) { + item.setIcon(null); + } else if (wantIcons && icon == null) { + item.setIcon (BLANK_ICON); + } + } + } + + protected static Boolean defaultHasIcon (JComponent comp, Boolean hasIcon) { + if (hasIcon != null && hasIcon) { + return hasIcon; + } + if (comp instanceof JMenuItem) { + Icon icon = ((JMenuItem) comp).getIcon(); + if (icon != null && BLANK_ICON != icon) { + return true; + } + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ModelEntry other = (ModelEntry) obj; + if (this.file() != other.file() && (this.file() == null || !this.file().equals(other.file()))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 31 * hash + (this.file != null ? this.file.hashCode() : 0); + return hash; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/PresenterEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/PresenterEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,86 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.io.IOException; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +public class PresenterEntry extends ThreadUnconstrainedModelEntry { + private DynamicMenuContent dmc; + PresenterEntry (DataObject dob) { + super (dob, JComponent.class, Presenter.Menu.class); + } + + @Override + protected JComponent[] findComponents() { + try { + Presenter.Menu p = super.get(); + JMenuItem c = p.getMenuPresenter(); + if (c instanceof DynamicMenuContent) { //ToolsAction.Inline + dmc = (DynamicMenuContent) c; + return dmc.getMenuPresenters(); + } + return new JComponent[] { c }; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JComponent[] { new JMenuItem("Error fetching " + file().getName()) }; //XXX + } + + @Override + protected JComponent[] refresh(JComponent[] old) { + if (dmc != null) { + return dmc.synchMenuPresenters(old); + } + return old; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.util.List; +import javax.swing.JSeparator; +import org.openide.loaders.DataObject; + +/** + * + * @author Tim Boudreau + */ +final class SeparatorModelEntry extends FileModelEntry { + SeparatorModelEntry(DataObject dob) { + super (dob, JSeparator.class, JSeparator.class); + } + + @Override + protected JSeparator findComponent() { + return new JSeparator(); + } + + @Override + protected JSeparator refreshComponent(JSeparator old) { + return old == null ? findComponent() : old; + } + + @Override + protected boolean initBackground(List> children) { + return false; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JComponent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +public abstract class ThreadUnconstrainedModelEntry extends FileModelEntry { + private final AtomicReference ref = new AtomicReference(); + + ThreadUnconstrainedModelEntry(DataObject dob, Class componentType, Class modelObjectType) { + super(dob, componentType, modelObjectType); + } + + @Override + protected final T get() throws IOException, ClassNotFoundException { + T result = ref.get(); + if (result == null) { + result = super.get(); + ref.set(result); + } + return result; + } + + @Override + protected boolean initBackground(List> children) { + //Since it is non-gui, instantiate the Action in the background + try { + ref.set(super.get()); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return false; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/layer.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/layer.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/DynAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/DynAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,84 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; + +/** + * + * @author Tim Boudreau + */ +public class DynAction implements DynamicMenuContent { + private static final int count = 5; + static int total; + final int index; + public DynAction() { + index = total++; + } + + @Override + public JComponent[] getMenuPresenters() { + JComponent[] result = new JComponent[count]; + for (int i= 0; i < result.length; i++) { + JMenuItem j = new JMenuItem ("Dynamic " + index + ":" + i); + j.putClientProperty("caption", j.getText()); + j.putClientProperty("syncCount", 0); + result[i] = j; + } + return result; + } + + @Override + public JComponent[] synchMenuPresenters(JComponent[] items) { + for (JComponent jc : items) { + JMenuItem m = (JMenuItem) jc; + String cap = (String) m.getClientProperty("caption"); + int syncs = ((Integer) m.getClientProperty("syncCount")).intValue(); + syncs ++; + m.putClientProperty("syncCount", syncs); + m.setText (cap + " (sync " + syncs + ")"); + } + return items; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,203 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseEvent; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.swing.AbstractButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import org.junit.Test; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.XMLFileSystem; + +/** + * + * @author tim + */ +public class MenuProviderImplTest extends NbTestCase { + + private MenuProviderImpl m; + private JMenuBar bar; + private JFrame jf; + private JMenu menu; + + public MenuProviderImplTest(String name) { + super(name); + } + + @Override + protected boolean runInEQ() { + return false; + } + + @Override + public void setUp() throws Exception { + FileSystem fs = new XMLFileSystem(MenuProviderImplTest.class.getResource("fakelayer.xml")); + m = new MenuProviderImpl(fs.getRoot().getFileObject("Menu")); + R r = new R(); + + EventQueue.invokeAndWait(r); + r.latch.await(20000, TimeUnit.MILLISECONDS); + while (!jf.isShowing()) { + Thread.sleep(30); + } + assertFalse(bar.getMenuCount() == 0); + menu = bar.getMenu(0); + } + + @Override + public void tearDown() { + jf.dispose(); + } + + private void click(final JComponent comp, final boolean showMenu) throws Exception { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + ((AbstractButton) comp).getModel().setPressed(showMenu); + MouseEvent evt = new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + evt = new MouseEvent(comp, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + evt = new MouseEvent(comp, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + } + }); + if (comp instanceof JMenu) { + JMenu m = (JMenu) comp; + while (showMenu != m.getPopupMenu().isShowing()) { + Thread.sleep (100); + } + } + } + + @Test + public void testMenuItemsAreNotStronglyReferenced() throws Exception { + assertNotNull (menu); + click (menu, true); + JPopupMenu popup = menu.getPopupMenu(); + Component[] c = popup.getComponents(); + assertEquals (7, c.length); + List> l = new ArrayList>(); + boolean presenterActionFound = false; + boolean regularActionFound = false; + int pActionIx = -1; + int rActionIx = -1; + for (int i=0; i < c.length; i++) { + Component cc = c[i]; + boolean wasPactionFound = presenterActionFound; + presenterActionFound |= cc instanceof PresenterAction.Marker; + if (!wasPactionFound && presenterActionFound) { + pActionIx = i; + } + l.add (new WeakReference(cc)); + boolean wasRactionFound = regularActionFound; + regularActionFound |= cc instanceof JMenuItem && ((JMenuItem) cc).getText().equals("Regular Action"); + if (!wasRactionFound && regularActionFound) { + rActionIx = i; + } + } + //Test order while we're here + assertTrue (presenterActionFound); + assertTrue (regularActionFound); + assertEquals ("Wrong index for presenter action", 5, pActionIx); + assertEquals ("Wrong index for presenter action", 6, rActionIx); + click (menu, false); + c = null; + for (Reference r : l) { + assertGC("Menu component not collected", r); + } + } + private static final int EXPECTED_MENU_COUNT = 1; //update if more added to xml + + private class R implements Runnable, ContainerListener { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void run() { + jf = new JFrame(); + bar = m.createMenu(jf); + bar.addContainerListener(this); + JPanel pnl = new JPanel(); + pnl.setMinimumSize(new Dimension(300, 300)); + pnl.setPreferredSize(new Dimension(300, 300)); + jf.setContentPane(pnl); + jf.setJMenuBar(bar); + jf.pack(); + jf.setVisible(true); + } + + @Override + public void componentAdded(ContainerEvent e) { + int count = e.getContainer().getComponentCount(); + if (count == EXPECTED_MENU_COUNT) { + latch.countDown(); + e.getContainer().invalidate(); + ((JMenuBar) e.getContainer()).revalidate(); + e.getContainer().repaint(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { + //do nothing + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/PresenterAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/PresenterAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.JMenuItem; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +public class PresenterAction extends AbstractAction implements Presenter.Menu { + + @Override + public JMenuItem getMenuPresenter() { + return new Marker ("Menu Presenter"); + } + + @Override + public void actionPerformed(ActionEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public class Marker extends JMenuItem { + Marker (String s) { + super (s); + } + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/RegularAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/RegularAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; + +/** + * + * @author Tim Boudreau + */ +public class RegularAction extends AbstractAction { + private boolean performed; + public RegularAction() { + putValue (NAME, "Regular Action"); + } + + @Override + public void actionPerformed(ActionEvent e) { + performed = true; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/fakelayer.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/fakelayer.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 nbbuild/cluster.properties --- a/nbbuild/cluster.properties Fri Mar 26 13:27:09 2010 -0400 +++ b/nbbuild/cluster.properties Sat Mar 27 04:32:30 2010 -0400 @@ -184,6 +184,7 @@ nb.cluster.platform.depends= nb.cluster.platform=\ api.annotations.common,\ + api.menus,\ api.progress,\ api.visual,\ applemenu,\ @@ -192,6 +193,7 @@ core.execution,\ core.io.ui,\ core.kit,\ + core.menu,\ core.multiview,\ core.nativeaccess,\ core.netigso,\ # HG changeset patch # User tboudreau@netbeans.org # Date 1269678969 14400 # Branch menus-rewrite-182698 # Node ID fa24874d2b7b2b28c14edef12c11d4300074add5 # Parent a60721f477e2c24c94a57db36e4563a2c870cc7d #182698 - main window to use new API, not MenuBar diff -r a60721f477e2 -r fa24874d2b7b core.windows/manifest.mf --- a/core.windows/manifest.mf Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/manifest.mf Sat Mar 27 04:36:09 2010 -0400 @@ -6,5 +6,5 @@ OpenIDE-Module-Recommends: org.netbeans.core.windows.nativeaccess.NativeWindowSystem AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 2.20 +OpenIDE-Module-Specification-Version: 2.21 diff -r a60721f477e2 -r fa24874d2b7b core.windows/nbproject/project.xml --- a/core.windows/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/nbproject/project.xml Sat Mar 27 04:36:09 2010 -0400 @@ -47,6 +47,15 @@ org.netbeans.core.windows + org.netbeans.api.menus + + + + 1 + 1.0 + + + org.netbeans.core diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java --- a/core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java Sat Mar 27 04:36:09 2010 -0400 @@ -45,9 +45,7 @@ import java.awt.EventQueue; import org.netbeans.core.WindowSystem; -import org.netbeans.core.windows.persistence.PersistenceManager; import org.netbeans.core.windows.services.DialogDisplayerImpl; -import org.netbeans.core.windows.view.ui.MainWindow; import org.openide.util.lookup.ServiceProvider; @@ -61,7 +59,6 @@ public void init() { assert !EventQueue.isDispatchThread(); - MainWindow.init(); } public void load() { diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java --- a/core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java Sat Mar 27 04:36:09 2010 -0400 @@ -122,6 +122,7 @@ public MainWindow getMainWindow() { if (mainWindow == null) { mainWindow = new MainWindow(); + mainWindow.init(); } return mainWindow; } diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sat Mar 27 04:36:09 2010 -0400 @@ -62,6 +62,7 @@ import javax.swing.JPanel; import javax.swing.border.*; import javax.swing.event.*; +import org.netbeans.api.menus.MenuProvider; import org.netbeans.core.windows.*; import org.netbeans.core.windows.view.ui.toolbars.ToolbarConfiguration; import org.openide.LifecycleManager; @@ -138,7 +139,7 @@ } } - public static void init() { + public void init() { if (mainMenuBar == null) { mainMenuBar = createMenuBar(); ToolbarPool.getDefault().waitFinished(); @@ -365,8 +366,12 @@ ); } + private JMenuBar createMenuBar() { + return MenuProvider.installMenu(this); + } + /** Creates menu bar. */ - private static JMenuBar createMenuBar() { + private static JMenuBar xcreateMenuBar() { JMenuBar menu = getCustomMenuBar(); if (menu == null) { menu = new MenuBar (null); # HG changeset patch # User tboudreau@netbeans.org # Date 1269681128 14400 # Branch menus-rewrite-182698 # Node ID d76d479b5dc8583f8edb2c259b1b6dbd9d58ccb1 # Parent fa24874d2b7b2b28c14edef12c11d4300074add5 Move folder listening logic to shared superclass for menus and menu bars diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -52,6 +52,7 @@ import java.util.Set; import javax.swing.Action; import javax.swing.JComponent; +import javax.swing.JMenuItem; import javax.swing.JSeparator; import org.openide.awt.DynamicMenuContent; import org.openide.cookies.InstanceCookie; @@ -267,6 +268,9 @@ if (icons != null) { ModelEntry.configureIcon(c, icons); } + if (c instanceof JMenuItem) { + c.setToolTipText(null); + } } } @@ -299,7 +303,7 @@ : ck == null ? false : type.isAssignableFrom(ck.instanceClass()); } - ModelEntry createEntry(DataObject ob, Callback callback) { + ModelEntry createEntry(DataObject ob, FolderModelEntry entry) { // System.err.println("Create " + name() + " entry for " + ob.getPrimaryFile().getPath()); switch (this) { case SEPARATOR: @@ -325,7 +329,7 @@ DataFolder fld = ob instanceof DataFolder ? (DataFolder) ob : ob.getLookup().lookup(DataFolder.class); Parameters.notNull("folder was non-null but is now null", fld); - return new MenuEntry(fld, null); + return new MenuEntry(fld, MenuManager.rp); case COMPONENT: return new ComponentModelEntry(ob); default: diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- a/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 05:12:08 2010 -0400 @@ -39,14 +39,7 @@ package org.netbeans.core.menu; -import java.awt.EventQueue; -import java.util.LinkedList; -import java.util.List; import javax.swing.JMenuBar; -import org.openide.filesystems.FileAttributeEvent; -import org.openide.filesystems.FileChangeAdapter; -import org.openide.filesystems.FileEvent; -import org.openide.filesystems.FileRenameEvent; import org.openide.loaders.DataFolder; import org.openide.util.RequestProcessor; @@ -54,20 +47,11 @@ * * @author Tim Boudreau */ -final class MenuBarModel extends FolderModelEntry { +final class MenuBarModel extends RefreshingFolderModelEntry { private final JMenuBar bar; - private FCA fca = new FCA(); - private final RequestProcessor.Task task; - private static final int DELAY = 100; MenuBarModel (DataFolder rootFolder, JMenuBar bar, RequestProcessor rp) { - super (rootFolder, JMenuBar.class); + super (rootFolder, JMenuBar.class, rp); this.bar = bar; - this.task = rp.create(fca); - rootFolder.getPrimaryFile().addFileChangeListener(fca); - } - - void init() { - fca.refresh(); } @Override @@ -76,75 +60,7 @@ } @Override - protected void onInitBackground(List> children) { - super.onInitBackground(children); - if (!refreshing) { - EventQueue.invokeLater(fca); - } - } - - volatile boolean refreshing; - private final class FCA extends FileChangeAdapter implements Runnable { - private volatile boolean enqueued; - boolean refresh() { - boolean result; - synchronized (this) { - result = enqueued; - if (!enqueued) { - enqueued = true; - task.schedule(DELAY); - } - return result; - } - } - - @Override - public void run() { - if (!EventQueue.isDispatchThread()) { - refreshing = true; - try { - List> l = new LinkedList>(); - initBackground(l); - setChildren(l); - } finally { - refreshing = false; - EventQueue.invokeLater(this); - } - } else { - enqueued = false; - bar.removeAll(); - MenuBarModel.this.refresh(new JMenuBar[] { bar }); - } - } - - @Override - public void fileAttributeChanged(FileAttributeEvent fe) { - refresh(); - } - - @Override - public void fileChanged(FileEvent fe) { - refresh(); - } - - @Override - public void fileDataCreated(FileEvent fe) { - refresh(); - } - - @Override - public void fileDeleted(FileEvent fe) { - refresh(); - } - - @Override - public void fileFolderCreated(FileEvent fe) { - refresh(); - } - - @Override - public void fileRenamed(FileRenameEvent fe) { - refresh(); - } + JMenuBar cachedComponent() { + return bar; } } diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- a/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -42,25 +42,23 @@ import java.awt.EventQueue; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.ButtonModel; import javax.swing.JMenu; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openide.awt.Mnemonics; import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; /** * * @author Tim Boudreau */ -final class MenuEntry extends FolderModelEntry implements ChangeListener { - +final class MenuEntry extends RefreshingFolderModelEntry implements ChangeListener { private Reference menu; - private final Callback callback; - - MenuEntry (DataFolder fld, Callback callback) { - super (fld, JMenu.class); - this.callback = callback == null ? this : callback; + MenuEntry (DataFolder fld, RequestProcessor rp) { + super (fld, JMenu.class, rp); } private JMenu menu() { @@ -81,6 +79,7 @@ } private boolean wasSelected = false; + private AtomicBoolean showing = new AtomicBoolean(); @Override public void stateChanged(ChangeEvent e) { ButtonModel mdl = (ButtonModel) e.getSource(); @@ -88,19 +87,22 @@ //XXX wasSelected could be true if the old component was garbage collected //without resetting its model - possible? if (selected && !wasSelected) { - if (callback != null) { - callback.onShow(this); - } + showing.set(true); + onShow(this); refreshComponent(menu()); } else if (wasSelected && !selected) { + showing.set(false); JMenu m = menu == null ? null : menu.get(); if (m != null) { m.removeAll(); } - if (callback != null) { - callback.onHide(this); - } + onHide(this); } wasSelected = selected; } + + @Override + JMenu cachedComponent() { + return menu(); + } } diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuManager.java --- a/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 05:12:08 2010 -0400 @@ -49,7 +49,8 @@ */ final class MenuManager { private final JMenuBar bar; - private final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); + //XXX make this non-static and pass to model entries + static final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); private final MenuBarModel model; MenuManager(DataFolder folder, JMenuBar bar) { diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.util.LinkedList; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JMenuBar; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +abstract class RefreshingFolderModelEntry extends FolderModelEntry { + + private final RequestProcessor.Task task; + private FCA fca = new FCA(); + private static final int DELAY = 100; + + RefreshingFolderModelEntry(DataFolder dob, Class compType, RequestProcessor rp) { + super(dob, compType); + this.task = rp.create(fca); + FileObject fo = dob.getPrimaryFile(); + fo.addFileChangeListener(FileUtil.weakFileChangeListener(fca, fo)); + } + + @Override + protected void onInitBackground(List> children) { + super.onInitBackground(children); + if (!refreshing) { + EventQueue.invokeLater(fca); + } + } + + abstract T cachedComponent(); + + void init() { + fca.refresh(); + } + volatile boolean refreshing; + + private final class FCA extends FileChangeAdapter implements Runnable { + + private volatile boolean enqueued; + + boolean refresh() { + boolean result; + synchronized (this) { + result = enqueued; + if (!enqueued) { + enqueued = true; + task.schedule(DELAY); + } + return result; + } + } + + @Override + public void run() { + if (!EventQueue.isDispatchThread()) { + refreshing = true; + try { + List> l = new LinkedList>(); + initBackground(l); + setChildren(l); + } finally { + refreshing = false; + EventQueue.invokeLater(this); + } + } else { + enqueued = false; + T comp = cachedComponent(); + if (comp != null) { + comp.removeAll(); + RefreshingFolderModelEntry.this.refresh(toCompTypeArray(new Object[]{comp})); + } + } + } + + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + refresh(); + } + + @Override + public void fileChanged(FileEvent fe) { + refresh(); + } + + @Override + public void fileDataCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileDeleted(FileEvent fe) { + refresh(); + } + + @Override + public void fileFolderCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileRenamed(FileRenameEvent fe) { + refresh(); + } + } +} # HG changeset patch # User tboudreau@netbeans.org # Date 1269723437 14400 # Branch menus-rewrite-182698 # Node ID b3556c2449f0164ac1628e8398bea7000f29168e # Parent d76d479b5dc8583f8edb2c259b1b6dbd9d58ccb1 Tests to ensure file changes are noticed and open popup menus are updated diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- a/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 05:12:08 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 16:57:17 2010 -0400 @@ -78,6 +78,20 @@ return menu(); } + @Override + protected void onRefresh(JMenu[] newComps) { + if (showing.get()) { + JMenu m = menu(); + if (m != null && m.isPopupMenuVisible()) { + m.setPopupMenuVisible(false); + m.invalidate(); + m.revalidate(); + m.repaint(); + m.setPopupMenuVisible(true); + } + } + } + private boolean wasSelected = false; private AtomicBoolean showing = new AtomicBoolean(); @Override diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/src/org/netbeans/core/menu/ModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 05:12:08 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 16:57:17 2010 -0400 @@ -70,7 +70,8 @@ protected abstract boolean initBackground(List> children); public String getName() { - return file().getNodeDelegate().getDisplayName(); + DataObject file = file(); + return file.isValid() ? file.getNodeDelegate().getDisplayName() : file.getName(); } protected final DataObjectType file() { @@ -95,8 +96,14 @@ return refresh (old); } + protected void onRefresh (CompType[] newComps) { + + } + protected CompType[] refresh (CompType[] old) { - return toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + CompType[] result = toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + onRefresh (result); + return result; } protected CompType[] findComponents() { diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/AAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/AAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class AAction extends AbstractAction { + public AAction() { + putValue (NAME, "A"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABAction extends AbstractAction { + public ABAction() { + putValue (NAME, "AB"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABCAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABCAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABCAction extends AbstractAction { + public ABCAction() { + putValue (NAME, "ABC"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B, Layers.C); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABCDAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABCDAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABCDAction extends AbstractAction { + public ABCDAction() { + putValue (NAME, "ABCD"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B, Layers.C, Layers.D); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/EventBlocker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/EventBlocker.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,96 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.AWTEvent; +import static java.awt.AWTEvent.*; +import java.awt.Component; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.openide.util.Exceptions; + +/** + * Blocks real input events from the user, so user's focus behavior cannot + * affect tests + * + * @author Tim Boudreau + */ +public class EventBlocker implements AWTEventListener { + private static final long MASK = FOCUS_EVENT_MASK | MOUSE_EVENT_MASK | MOUSE_MOTION_EVENT_MASK | MOUSE_WHEEL_EVENT_MASK | + KEY_EVENT_MASK | ACTION_EVENT_MASK | ADJUSTMENT_EVENT_MASK | + INPUT_METHOD_EVENT_MASK | ITEM_EVENT_MASK | TEXT_EVENT_MASK; + static void init() { + Toolkit.getDefaultToolkit().addAWTEventListener(new EventBlocker(), MASK); + } + + @Override + public void eventDispatched(AWTEvent event) { + Class c = event.getClass(); + if (event instanceof ME) { + return; + } + try { + Method m = c.getMethod("consume"); + try { + m.invoke(event); + } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException ex) { + } catch (InvocationTargetException ex) { + } + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } + } + + public static MouseEvent mouseEvent (Component c, int id) { + return new ME(c, id); + } + + private static final class ME extends MouseEvent { + ME(Component comp, int id) { + super (comp, id, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + } + } + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/FileChangeTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/FileChangeTest.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,303 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseEvent; +import java.net.URL; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import javax.swing.AbstractButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import org.junit.Test; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.MultiFileSystem; +import org.openide.filesystems.XMLFileSystem; +import org.xml.sax.SAXException; + +/** + * + * @author tim + */ +public class FileChangeTest extends NbTestCase { + + private MenuProviderImpl m; + private JMenuBar bar; + private JFrame jf; + private JMenu menu; + + + public FileChangeTest(String name) { + super(name); + } + + @Override + protected boolean runInEQ() { + return false; + } + + @Override + public void setUp() throws Exception { + FileSystem fs = new MFS(); + m = new MenuProviderImpl(fs.getRoot().getFileObject("Menu")); + R r = new R(); + + EventQueue.invokeAndWait(r); + r.latch.await(20000, TimeUnit.MILLISECONDS); + while (!jf.isShowing()) { + Thread.sleep(30); + } + while (bar.getMenuCount() < 2) { + Thread.sleep(30); + } + assertFalse(bar.getMenuCount() == 0); + menu = bar.getMenu(0); + } + + @Override + public void tearDown() { + jf.dispose(); + } + + private void click(final JComponent comp, final boolean showMenu) throws Exception { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + ((AbstractButton) comp).getModel().setPressed(showMenu); + MouseEvent evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_PRESSED); + comp.dispatchEvent(evt); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_RELEASED); + comp.dispatchEvent(evt); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_CLICKED); + comp.dispatchEvent(evt); + } + }); + if (comp instanceof JMenu) { + JMenu m = (JMenu) comp; + while (showMenu != m.getPopupMenu().isShowing()) { + Thread.sleep (100); + } + } + } + + + @Test + public void testMenusAreUpdatedWhenOpen() throws Exception { + EventBlocker.init(); + assertNotNull (menu); + click (menu, true); + JPopupMenu popup = menu.getPopupMenu(); + Component[] c = popup.getComponents(); + assertTrue (menu.isPopupMenuVisible()); + assertEquals (4, popup.getComponentCount()); + final CountDownLatch l = new CountDownLatch(2); + popup.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("added - count now " + e.getContainer().getComponentCount()); + if (e.getContainer().getComponentCount() == 5) { + l.countDown(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("removed - count now " + e.getContainer().getComponentCount()); + } + + }); + bar.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("bar comp added - count now " + e.getContainer().getComponentCount()); + if (e.getContainer().getComponentCount() == 3) { + l.countDown(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("bar comp removed - count now " + e.getContainer().getComponentCount()); + } + + }); + + new ABCDAction().actionPerformed(null); + l.await(20000, TimeUnit.MILLISECONDS); + final AtomicInteger popupCompCount = new AtomicInteger(-1); + final AtomicInteger barCompCount = new AtomicInteger(-1); + final AtomicBoolean vis = new AtomicBoolean(); + EventQueue.invokeAndWait (new Runnable() { + + @Override + public void run() { + popupCompCount.set(menu.getPopupMenu().getComponentCount()); + barCompCount.set(bar.getComponentCount()); + vis.set(menu.getPopupMenu().isShowing()); + } + + }); + + assertTrue (vis.get()); + assertEquals (5, popupCompCount.get()); + assertEquals (3, barCompCount.get()); + + popupCompCount.set(-1); + barCompCount.set(-1); + vis.set(false); + + final CountDownLatch ll = new CountDownLatch(4); + popup.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("added - count now " + e.getContainer().getComponentCount()); + ll.countDown(); + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("removed - count now " + e.getContainer().getComponentCount()); + } + + }); + new AAction().actionPerformed(null); + ll.await(20000, TimeUnit.MILLISECONDS); + EventQueue.invokeAndWait (new Runnable() { + + @Override + public void run() { + popupCompCount.set(menu.getPopupMenu().getComponentCount()); + barCompCount.set(bar.getComponentCount()); + vis.set(menu.getPopupMenu().isShowing()); + } + + }); + + assertTrue (vis.get()); + assertEquals (4, popupCompCount.get()); + assertEquals (2, barCompCount.get()); + +// Thread.sleep (40000); + + } + private static final int EXPECTED_MENU_COUNT = 1; //update if more added to xml + + private class R implements Runnable, ContainerListener { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void run() { + jf = new JFrame(); + bar = m.createMenu(jf); + bar.addContainerListener(this); + JPanel pnl = new JPanel(); + pnl.setMinimumSize(new Dimension(300, 300)); + pnl.setPreferredSize(new Dimension(300, 300)); + jf.setContentPane(pnl); + jf.setJMenuBar(bar); + jf.pack(); + jf.setVisible(true); + } + + @Override + public void componentAdded(ContainerEvent e) { + int count = e.getContainer().getComponentCount(); + if (count == EXPECTED_MENU_COUNT) { + latch.countDown(); + e.getContainer().invalidate(); + ((JMenuBar) e.getContainer()).revalidate(); + e.getContainer().repaint(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { + //do nothing + } + } + + public static final class MFS extends MultiFileSystem { + public static MFS INSTANCE; + MFS() throws SAXException { + setLayers (Layers.A); + INSTANCE = this; + } + + public void setLayers(Layers... l) throws SAXException { + System.err.println("setLayers " + Arrays.asList(l)); + FileSystem[] xfs = new XMLFileSystem[l.length]; + for (int i = 0; i < xfs.length; i++) { + xfs[i] = new XMLFileSystem(l[i].res()); + } + setDelegates(xfs); + } + } + + public enum Layers { + A("a.xml"), + B("b.xml"), + C("c.xml"), + D("d.xml"); + private String resource; + Layers (String resource) { + this.resource = resource; + } + + public URL res() { + return Layers.class.getResource(resource); + } + } +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/a.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/a.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/b.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/b.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/c.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/c.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/d.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/d.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,12 @@ + + + + + + + + + + + + # HG changeset patch # User tboudreau@netbeans.org # Date 1269725476 14400 # Branch menus-rewrite-182698 # Node ID dd5ee1067e5247027b0535bdc9d6584e4df0f2c3 # Parent b3556c2449f0164ac1628e8398bea7000f29168e Get rid of menu warm-up, which is no longer needed. Eliminate unnecessary refresh of initial dynamic content. diff -r b3556c2449f0 -r dd5ee1067e52 core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 16:57:17 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 17:31:16 2010 -0400 @@ -60,8 +60,6 @@ try { DynamicMenuContent dyn = get(); JComponent[] result = dyn.getMenuPresenters(); - //Necessary the first time, or we get bogus elements - dyn.synchMenuPresenters(result); return result; } catch (IOException ex) { Exceptions.printStackTrace(ex); diff -r b3556c2449f0 -r dd5ee1067e52 core.ui/manifest.mf --- a/core.ui/manifest.mf Sat Mar 27 16:57:17 2010 -0400 +++ b/core.ui/manifest.mf Sat Mar 27 17:31:16 2010 -0400 @@ -4,5 +4,5 @@ OpenIDE-Module-Layer: org/netbeans/core/ui/resources/layer.xml AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 1.21 +OpenIDE-Module-Specification-Version: 1.22 diff -r b3556c2449f0 -r dd5ee1067e52 core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java --- a/core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java Sat Mar 27 16:57:17 2010 -0400 +++ b/core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java Sat Mar 27 17:31:16 2010 -0400 @@ -45,6 +45,7 @@ import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; @@ -58,7 +59,6 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; -import javax.swing.SwingUtilities; import javax.swing.text.html.HTMLEditorKit; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; @@ -74,58 +74,24 @@ import org.openide.util.NbBundle; /** - * A menu preheating task. It is referenced from the layer and may be performed - * by the core after the startup. - * - * Plus hooked WindowListener on main window (see {@link NbWindowsAdapter}) + * Triggers filesystem refresh on main window activation. */ public final class MenuWarmUpTask implements Runnable { - private Component[] comps; - - /** Actually performs pre-heat. - */ @Override public void run() { try { - SwingUtilities.invokeAndWait(new Runnable() { + EventQueue.invokeLater(new Runnable() { @Override public void run() { Frame main = WindowManager.getDefault().getMainWindow(); - assert main != null; main.addWindowListener(new NbWindowsAdapter()); - - if (main instanceof JFrame) { - comps = ((JFrame) main).getJMenuBar().getComponents(); - } } }); } catch (Exception e) { // bail out! return; } - - - if (comps != null) { - walkMenu(comps); - comps = null; - } - - // tackle the Tools menu now? How? - } - - private void walkMenu(Component[] items) { - for (int i=0; i cls = items[i].getClass(); - Method m = cls.getDeclaredMethod("doInitialize"); - m.setAccessible(true); - m.invoke(items[i]); - walkMenu(((JMenu)items[i]).getMenuComponents()); // recursive? - } catch (Exception e) {// do nothing, it may happen for user-provided menus - } - } } /** # HG changeset patch # User tboudreau@netbeans.org # Date 1269728975 14400 # Branch menus-rewrite-182698 # Node ID 7a1c5c06f75b452207dd55626a65909e51f2bab3 # Parent dd5ee1067e5247027b0535bdc9d6584e4df0f2c3 Avoid unnecessary submenu creation on startup. Move entire menu init to after main window is shown (may not be viable on slow systems, but worth trying). MenuProviderTest now uses user-initiated AWT event blocking to avoid user-interference with test passing. diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -57,7 +57,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { return false; } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -142,12 +142,12 @@ } } - protected void onInitBackground(List> children) { + protected void onInitBackground(List> children, int currDepth) { //do nothing - for subclasses to force init } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { // System.err.println("initBackground " + getName()); DataFolder fld = file(); boolean changed = true; @@ -165,7 +165,7 @@ children.add(e); //XXX defer or batch afterward? List> kids = new ArrayList>(); - boolean hasKids = e.initBackground(kids); + boolean hasKids = e.initBackground(kids, currDepth+1); changed |= hasKids; if (hasKids) { if (e instanceof FolderModelEntry) { @@ -185,7 +185,7 @@ } } } finally { - onInitBackground(children); + onInitBackground(children, currDepth); } return changed; } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- a/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 18:29:35 2010 -0400 @@ -39,6 +39,8 @@ package org.netbeans.core.menu; +import java.util.List; +import javax.swing.JComponent; import javax.swing.JMenuBar; import org.openide.loaders.DataFolder; import org.openide.util.RequestProcessor; @@ -63,4 +65,18 @@ JMenuBar cachedComponent() { return bar; } + + @Override + protected void installComponents(List components, JMenuBar into) { + synchronized (bar.getTreeLock()) { + super.installComponents(components, into); + } + bar.invalidate(); + bar.revalidate(); + bar.repaint(); + } + + + + } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java --- a/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 18:29:35 2010 -0400 @@ -40,6 +40,10 @@ package org.netbeans.core.menu; import java.awt.EventQueue; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; import javax.swing.JFrame; import javax.swing.JMenuBar; import org.netbeans.api.menus.MenuProvider; @@ -71,11 +75,20 @@ } FileObject menuFO = menuFolder; DataFolder menuFolder = DataFolder.findFolder(menuFO); - JMenuBar bar = new JMenuBar(); + final JMenuBar bar = new JMenuBar(); bar.setBorderPainted(false); - MenuManager mgr = new MenuManager(menuFolder, bar); - mgr.init(); + final MenuManager mgr = new MenuManager(menuFolder, bar); + bar.addHierarchyListener(new HierarchyListener() { + + @Override + public void hierarchyChanged(HierarchyEvent e) { + if (bar.isShowing()) { + bar.removeHierarchyListener(this); + mgr.init(); + } + } + }); +// mgr.init(); return bar; } - } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -67,7 +67,7 @@ Parameters.notNull("file", file); } - protected abstract boolean initBackground(List> children); + protected abstract boolean initBackground(List> children, int currDepth); public String getName() { DataObject file = file(); @@ -136,12 +136,41 @@ Icon icon = item.getIcon(); if (!wantIcons && icon != null) { item.setIcon(null); + item.setDisabledIcon(null); + item.setDisabledSelectedIcon(null); } else if (wantIcons && icon == null) { item.setIcon (BLANK_ICON); + item.setDisabledIcon(BLANK_ICON); + item.setDisabledSelectedIcon(BLANK_ICON); } +// String txt = item.getText(); +// if (txt != null && txt.contains("Find Issues")) { +// Thread.dumpStack(); +// log (item); +// } } } +//Useful for diagnosing strangely painting menu items +// private static void log (JMenuItem item) { +// System.err.println("FIND ISSUES ITEM: " + item); +// logType (item); +// System.err.println(" Action:"); +// logType (item.getAction()); +// } +// +// private static void logType (Object o) { +// if (o == null) return; +// for (Class c : o.getClass().getInterfaces()) { +// System.err.println(" I:" + c.getName()); +// } +// Class x = o.getClass(); +// while (x != Object.class) { +// System.err.println(" C: " + x.getName()); +// x = x.getSuperclass(); +// } +// } + protected static Boolean defaultHasIcon (JComponent comp, Boolean hasIcon) { if (hasIcon != null && hasIcon) { return hasIcon; @@ -176,4 +205,9 @@ hash = 31 * hash + (this.file != null ? this.file.hashCode() : 0); return hash; } + + @Override + public String toString() { + return super.toString() + "[" + getName() + "]"; + } } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -42,7 +42,6 @@ import java.util.LinkedList; import java.util.List; import javax.swing.JComponent; -import javax.swing.JMenuBar; import org.openide.filesystems.FileAttributeEvent; import org.openide.filesystems.FileChangeAdapter; import org.openide.filesystems.FileEvent; @@ -61,6 +60,7 @@ private final RequestProcessor.Task task; private FCA fca = new FCA(); private static final int DELAY = 100; + private static final boolean LOG_RUNNABLES = Boolean.getBoolean ("RefreshingFolderModelEntry.log"); RefreshingFolderModelEntry(DataFolder dob, Class compType, RequestProcessor rp) { super(dob, compType); @@ -70,10 +70,10 @@ } @Override - protected void onInitBackground(List> children) { - super.onInitBackground(children); - if (!refreshing) { - EventQueue.invokeLater(fca); + protected void onInitBackground(List> children, int currDepth) { + super.onInitBackground(children, currDepth); + if (!refreshing && currDepth == 0) { + EventQueue.invokeLater(LOG_RUNNABLES ? new LogRunnable(fca) : fca); } } @@ -84,6 +84,21 @@ } volatile boolean refreshing; + private final class LogRunnable implements Runnable { + Throwable t; + private final Runnable real; + LogRunnable (Runnable real) { + this.real = real; + t = new Exception("Refresh " + RefreshingFolderModelEntry.this).fillInStackTrace(); + } + + @Override + public void run() { + t.printStackTrace(); + real.run(); + } + } + private final class FCA extends FileChangeAdapter implements Runnable { private volatile boolean enqueued; @@ -94,7 +109,12 @@ result = enqueued; if (!enqueued) { enqueued = true; - task.schedule(DELAY); + if (!LOG_RUNNABLES) { + task.schedule(DELAY); + } else { + RequestProcessor.Task t = MenuManager.rp.create(new LogRunnable(this)); + t.schedule(DELAY); + } } return result; } @@ -106,11 +126,11 @@ refreshing = true; try { List> l = new LinkedList>(); - initBackground(l); + initBackground(l, 0); setChildren(l); } finally { refreshing = false; - EventQueue.invokeLater(this); + EventQueue.invokeLater(LOG_RUNNABLES ? new LogRunnable(this) : this); } } else { enqueued = false; diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -63,7 +63,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { return false; } } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -68,7 +68,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { //Since it is non-gui, instantiate the Action in the background try { ref.set(super.get()); diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java --- a/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 18:29:35 2010 -0400 @@ -108,14 +108,11 @@ public void run() { ((AbstractButton) comp).getModel().setPressed(showMenu); - MouseEvent evt = new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + MouseEvent evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_PRESSED); comp.dispatchEvent(evt); - evt = new MouseEvent(comp, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_RELEASED); comp.dispatchEvent(evt); - evt = new MouseEvent(comp, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_CLICKED); comp.dispatchEvent(evt); } }); @@ -129,6 +126,7 @@ @Test public void testMenuItemsAreNotStronglyReferenced() throws Exception { + EventBlocker.init(); assertNotNull (menu); click (menu, true); JPopupMenu popup = menu.getPopupMenu(); # HG changeset patch # User tboudreau@netbeans.org # Date 1269624429 14400 # Branch menus-rewrite-182698 # Node ID 1f7b9142066385d53b1e87b246fd40f591bfebae # Parent e60cab7ba0549eebc6a3cb913ecf4f2dcc29bae3 Initial branch # HG changeset patch # User tboudreau@netbeans.org # Date 1269678750 14400 # Branch menus-rewrite-182698 # Node ID a60721f477e2c24c94a57db36e4563a2c870cc7d # Parent 1f7b9142066385d53b1e87b246fd40f591bfebae #182698 - simplified rewrite of main menu infrastructure. Eliminate use of MenuBar and FolderInstance. diff -r 1f7b91420663 -r a60721f477e2 api.menus/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/build.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,5 @@ + + + Builds, tests, and runs the project org.netbeans.api.menus + + diff -r 1f7b91420663 -r a60721f477e2 api.menus/manifest.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/manifest.mf Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: false +OpenIDE-Module: org.netbeans.api.menus/1 +OpenIDE-Module-Localizing-Bundle: org/netbeans/api/menus/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 +OpenIDE-Module-Recommends: org.netbeans.api.menus.MenuProvider + diff -r 1f7b91420663 -r a60721f477e2 api.menus/nbproject/project.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/nbproject/project.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,4 @@ +is.autoload=true +javac.source=1.6 +javac.compilerargs=-Xlint -Xlint:-serial +nbm.module.author=Tim Boudreau diff -r 1f7b91420663 -r a60721f477e2 api.menus/nbproject/project.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,30 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.api.menus + + + org.openide.util + + + + 8.2 + + + + org.openide.util.lookup + + + + 8.2 + + + + + org.netbeans.api.menus + + + + diff -r 1f7b91420663 -r a60721f477e2 api.menus/src/org/netbeans/api/menus/Bundle.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/src/org/netbeans/api/menus/Bundle.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +OpenIDE-Module-Display-Category=Infrastructure +OpenIDE-Module-Long-Description=\ + API which allows another module to inject a factory which creates the main window's menu bar. +OpenIDE-Module-Name=Main Menu API +FILE_MENU=File +EXIT_MENU_ITEM=Exit +OpenIDE-Module-Short-Description=API for generating the application's menu(s) diff -r 1f7b91420663 -r a60721f477e2 api.menus/src/org/netbeans/api/menus/MenuProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.menus/src/org/netbeans/api/menus/MenuProvider.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,142 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.api.menus; + +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import org.openide.LifecycleManager; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.Parameters; + +/** + * Factory for the application window main menu. Registered in the default + * lookup. + * + * @author Tim Boudreau + * @since 1.0 + */ +public abstract class MenuProvider { + + /** + * Install a menu in a JFrame. Called by the window system to set up the + * main menu. This method is not guaranteed to return the same JMenuBar + * if called repeatedly, and should not be used as a way to look up the + * main window's menu. + *

+ * This method must be called on the AWT event dispatch thread. + *

+ * If no MenuProvider is registered in the default lookup, will create a + * menu which simply contains a File menu with an Exit menu item that + * uses LifecycleManager to cleanly exit the application. + * + * @param frame A JFrame. May not be null. + * @return The JMenuBar which has been installed in the frame. + */ + public static JMenuBar installMenu(JFrame frame) { + Parameters.notNull("frame", frame); //NOI18N + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); //NOI18N + } + MenuProvider p = Lookup.getDefault().lookup(MenuProvider.class); + JMenuBar menu = p == null ? fallbackMenu() : p.createMenu(frame); + if (p == null) { + Logger.getLogger(MenuProvider.class.getName()).log (Level.WARNING, + "No instance of {0} registered in the default lookup. " + //NOI18N + "Using fallback.", MenuProvider.class.getName()); //NOI18N + } + if (menu == null) { + if (p != null) { + Exceptions.printStackTrace(new NullPointerException(p + + " returned null menu")); //NOI18N + } + menu = fallbackMenu(); + } + frame.setJMenuBar(menu); + return menu; + } + + private static JMenuBar fallbackMenu() { + String fileMenuName = NbBundle.getMessage(MenuProvider.class, + "FILE_MENU"); //NOI18N + String exitItemName = NbBundle.getMessage(MenuProvider.class, + "EXIT_MENU_ITEM"); //NOI18N + JMenuBar result = new JMenuBar(); + JMenu fileMenu = new JMenu(fileMenuName); + Action exitAction = new ExitAction(); + exitAction.putValue(Action.NAME, exitItemName); + fileMenu.add(exitAction); + result.add(fileMenu); + return result; + } + + /** + * Create a menu for the provided JFrame. This method is not expected + * to cache the return value. It should not actually call the target frame's + * setJMenuBar() method - the target frame is provided as a + * convenience in case some multi-frame application provides menus for + * multiple frames. + *

+ * This method will only be called by NetBeans from the event dispatch thread. + * Constructing components is safe here, but it is preferable not to perform + * heavy-duty I/O in this method, but rather to create the initial menu bar + * quickly, and then populate its menus lazily or in the background. + *

+ * @param target The frame the menu should be created for + * @since 1.0 + * @return A JMenuBar + */ + protected abstract JMenuBar createMenu(JFrame target); + + private static final class ExitAction extends AbstractAction { + + @Override + public void actionPerformed(ActionEvent e) { + LifecycleManager.getDefault().exit(); + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/build.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,5 @@ + + + Builds, tests, and runs the project org.netbeans.core.menu + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/manifest.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/manifest.mf Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.netbeans.core.menu +OpenIDE-Module-Layer: org/netbeans/core/menu/layer.xml +OpenIDE-Module-Localizing-Bundle: org/netbeans/core/menu/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 +OpenIDE-Module-Provides: org.netbeans.api.menus.MenuProvider + diff -r 1f7b91420663 -r a60721f477e2 core.menu/nbproject/project.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/nbproject/project.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,2 @@ +javac.source=1.6 +javac.compilerargs=-Xlint -Xlint:-serial diff -r 1f7b91420663 -r a60721f477e2 core.menu/nbproject/project.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,83 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.core.menu + + + org.netbeans.api.menus + + + + 1 + 1.0 + + + + org.openide.awt + + + + 7.21 + + + + org.openide.filesystems + + + + 7.37 + + + + org.openide.loaders + + + + 7.13 + + + + org.openide.nodes + + + + 7.14 + + + + org.openide.util + + + + 8.2 + + + + org.openide.util.lookup + + + + 8.2 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ActionModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ActionModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,125 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import javax.swing.Action; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenuItem; +import org.openide.awt.AcceleratorBinding; +import org.openide.awt.Actions; +import org.openide.awt.Mnemonics; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.actions.BooleanStateAction; +import org.openide.util.actions.Presenter; +import org.openide.util.actions.SystemAction; + +/** + * + * @author Tim Boudreau + */ +final class ActionModelEntry extends ThreadUnconstrainedModelEntry { + ActionModelEntry(DataObject dob) { + super (dob, JMenuItem.class, Action.class); + } + + @Override + protected JMenuItem refreshComponent(JMenuItem old) { + Action a; + try { + a = get(); + if (old.getAction() == a) { + configureName (a, old); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return old; + } + + private String getActionName(Action action) { + String name = (String) action.getValue(Action.NAME); + if (name == null && action instanceof SystemAction) { + name = ((SystemAction) action).getName(); + } + name = name == null ? getName() : name; + return name; + } + + private void configureName (Action action, JMenuItem item) { + String txt = item.getText(); + if (txt == null || txt.length() == 0) { + Mnemonics.setLocalizedText(item, getActionName(action)); + item.setToolTipText(null); + } + } + + @Override + protected JMenuItem findComponent() { + try { + Action action = get(); + if (action instanceof Presenter.Menu) { + throw new IllegalStateException("ActionModelEntry used for a menu presenter action"); + } + JMenuItem item; + if (action instanceof BooleanStateAction) { + BooleanStateAction bse = (BooleanStateAction) action; + JCheckBoxMenuItem cbItem = new JCheckBoxMenuItem(); + configureName (action, cbItem); + Actions.connect(cbItem, bse, false); + item = cbItem; + } else { + item = new JMenuItem(); + configureName (action, item); + Actions.connect (item, action); + } + AcceleratorBinding.setAccelerator(action, file().getPrimaryFile()); + return item; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JMenuItem("Error from " + file().getName()); //XXX + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/Bundle.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/Bundle.properties Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,1 @@ +OpenIDE-Module-Name=Core Main Menu diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/Callback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/Callback.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +/** + * + * @author Tim Boudreau + */ +interface Callback { + + void onShow(ModelEntry entry); + + void onHide(ModelEntry entry); +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ComponentList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ComponentList.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,178 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import javax.swing.JComponent; +import javax.swing.JSeparator; +import org.openide.util.Parameters; + +/** + * Wrapper for an array of components which can be checked for value equality. + * + * @author Tim Boudreau + */ +final class ComponentList { + private final JComponent[] comps; + private final Reference[] refs; + private volatile boolean isWeak; + ComponentList(JComponent[] comps) { + Parameters.notNull("comps", comps); + this.comps = new JComponent[comps.length]; + refs = new Reference[comps.length]; + for (int i= 0; i < comps.length; i++) { + if (comps[i] == null) { + this.comps[i] = new JSeparator(); + } else { + this.comps[i] = comps[i]; + } + refs[i] = new WeakReference(this.comps[i]); + } + } + + JComponent get (int ix) { + JComponent result = comps[ix]; + if (comps[ix] == null) { + result = refs[ix] == null ? null : (JComponent) refs[ix].get(); + } + return result; + } + + private void set (int ix, JComponent comp) { + Parameters.notNull("comp", comp); //NOI18N + comps[ix] = comp; + refs[ix] = new WeakReference(comps[ix]); + } + + int size() { + return comps.length; + } + + public JComponent[] getComponents() { + JComponent[] result = new JComponent[size()]; + for (int i= 0; i < result.length; i++) { + result[i] = get(i); + } + return result; + } + + boolean addNotify() { + boolean result = false; + if (!result) { + result = true; + for (int i= 0; i < comps.length; i++) { + if (comps[i] == null) { + comps[i] = refs[i] == null ? null : (JComponent) refs[i].get(); + if (comps[i] == null) { + result = false; + } + } + } + } + isWeak = result; + return result; + } + + void removeNotify() { +// if (!isWeak) { + for (int i = 0; i < comps.length; i++) { + comps[i] = null; + } +// } + isWeak = true; + } + + boolean repair (JComponent[] nue) { + Parameters.notNull("nue", nue); + if (nue.length != comps.length) { + return false; + } + boolean result = true; + for (int i= 0; i < comps.length; i++) { + JComponent c = get(i); + if (c == null || c == nue[i]) { + if (nue[i] == null) { + throw new NullPointerException ("Null element in component " + //NOI18N + "list at " + i); //NOI18N + } + set (i, nue[i]); + } else { + result = false; + } + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != ComponentList.class) { + return false; + } + final ComponentList other = (ComponentList) obj; + if (other == this) { + return true; + } + if (other.comps == comps) { + return true; + } + if (other.comps.length != comps.length) { + return false; + } + for (int i=0; i < comps.length; i++) { + //test only on exact equality + JComponent a = get(i); + JComponent b = other.get(i); + if (a != b) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + Arrays.deepHashCode(this.comps); + return hash; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,82 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.io.IOException; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +final class ComponentModelEntry extends FileModelEntry { + ComponentModelEntry (DataObject dob) { + super (dob, JComponent.class, JComponent.class); + } + + @Override + protected boolean initBackground(List> children) { + return false; + } + + @Override + protected JComponent findComponent() { + try { + return get(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JMenuItem("Error getting comp for " + getName()); + } + + @Override + protected JComponent refreshComponent(JComponent old) { + return old; + } + + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,85 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +public class DynamicModelEntry extends ThreadUnconstrainedModelEntry { + DynamicModelEntry(DataObject dob) { + super (dob, JComponent.class, DynamicMenuContent.class); + } + + @Override + protected JComponent[] findComponents() { + try { + DynamicMenuContent dyn = get(); + JComponent[] result = dyn.getMenuPresenters(); + //Necessary the first time, or we get bogus elements + dyn.synchMenuPresenters(result); + return result; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JComponent[] { new JMenuItem("Error getting " + file().getName()) }; //XXX + } + + @Override + protected JComponent[] refresh(JComponent[] old) { + try { + return get().synchMenuPresenters(old); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return old; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/FileModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/FileModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,130 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.EventQueue; +import java.io.IOException; +import javax.swing.JComponent; +import org.openide.cookies.InstanceCookie; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; + +/** + * + * @author Tim Boudreau + */ +abstract class FileModelEntry extends ModelEntry { + protected final Class modelObjectType; + FileModelEntry(DataObject dob, Class componentType, Class modelObjectType) { + super (dob, DataObject.class, componentType); + this.modelObjectType = modelObjectType; + assert !(dob instanceof DataFolder) : "Folder used for menu item"; //NOI18N + } + + private final InstanceCookie cookie() { + DataObject dob = file(); + InstanceCookie ck = dob.getLookup().lookup(InstanceCookie.class); + if (ck == null) { + ck = new EmptyInstanceCookie(dob.getName()); + } + return ck; + } + + final boolean requiresEq() { + return Component.class.isAssignableFrom(dobType); + } + + protected T get() throws IOException, ClassNotFoundException { + if (requiresEq() && !EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); + } + return modelObjectType.cast(cookie().instanceCreate()); + } + + private static final class EmptyInstanceCookie implements InstanceCookie, InstanceCookie.Of { + private final String name; + EmptyInstanceCookie (String name) { + this.name = name; + } + + @Override + public String instanceName() { + return name; + } + + @Override + public Class instanceClass() throws IOException, ClassNotFoundException { + return Object.class; + } + + @Override + public Object instanceCreate() throws IOException, ClassNotFoundException { + return null; + } + + @Override + public boolean instanceOf(Class type) { + return false; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EmptyInstanceCookie other = (EmptyInstanceCookie) obj; + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,336 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.EventQueue; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JSeparator; +import org.openide.awt.DynamicMenuContent; +import org.openide.cookies.InstanceCookie; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.Parameters; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +abstract class FolderModelEntry extends ModelEntry implements Callback { + + private final List> children = new ArrayList>(15); + private final Object childLock = new Object(); + private final Object mapLock = new Object(); + private final Map, ComponentList> comps4model = + new HashMap, ComponentList>(); + + FolderModelEntry(DataFolder fld, Class compType) { + super(fld, DataFolder.class, compType); + } + + boolean setChildren(List> l) { + boolean result; + synchronized (childLock) { + result = !children.equals(l); + if (result) { + children.clear(); + children.addAll(l); + } + } + synchronized (mapLock) { + if (!comps4model.isEmpty()) { + Set> known = new HashSet>(comps4model.keySet()); + known.removeAll(l); + for (ModelEntry m : known) { + comps4model.remove(m); + } + } + } + return result; + } + + @Override + public final void onHide(ModelEntry entry) { + synchronized (mapLock) { + for (Map.Entry, ComponentList> e : comps4model.entrySet()) { + e.getValue().removeNotify(); + } + } + } + + @Override + public final void onShow(ModelEntry entry) { + synchronized (mapLock) { + for (Map.Entry, ComponentList> e : comps4model.entrySet()) { + e.getValue().addNotify(); + } + } + } + + private ComponentList components(ModelEntry e) { + Parameters.notNull("e", e); + synchronized (mapLock) { + return comps4model.get(e); + } + } + + private void setComponents (ModelEntry e, ComponentList comps) { + Parameters.notNull("comps", comps); + Parameters.notNull("e", e); + synchronized (mapLock) { + if (comps == null) { + comps4model.remove(e); + } else { + comps4model.put(e, comps); + } + } + } + + List> children() { + synchronized (childLock) { + return new ArrayList>(children); + } + } + + protected void onInitBackground(List> children) { + //do nothing - for subclasses to force init + } + + @Override + protected boolean initBackground(List> children) { +// System.err.println("initBackground " + getName()); + DataFolder fld = file(); + boolean changed = true; + Set> created = new HashSet>(); + try { + DataObject[] ch = fld.getChildren(); + if (ch != null) { //can be null + outer: + for (DataObject dob : ch) { + try { + for (Types t : Types.values()) { + if (t.accept(dob)) { + ModelEntry e = t.createEntry(dob, this); + created.add(e); + children.add(e); + //XXX defer or batch afterward? + List> kids = new ArrayList>(); + boolean hasKids = e.initBackground(kids); + changed |= hasKids; + if (hasKids) { + if (e instanceof FolderModelEntry) { + FolderModelEntry fme = FolderModelEntry.class.cast(e); + changed |= fme.setChildren(kids); + } + } + continue outer; + } + } +// System.err.println("No type accepted " + dob.getPrimaryFile().getPath() + " : " + ic(dob)); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + } + } + } finally { + onInitBackground(children); + } + return changed; + } + +// private static String ic(DataObject dob) throws ClassNotFoundException, IOException { +// InstanceCookie ck = dob.getCookie(InstanceCookie.class); +// return ck == null ? " [no ic]" : ck.instanceClass().getName(); +// } + @Override + protected CompType refreshComponent(CompType old) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); + } +// long start = System.currentTimeMillis(); + Parameters.notNull("old", old); + old.removeAll(); + List all = new LinkedList(); + List> kids = children(); + Boolean expectHasIcon = false; + for (ModelEntry e : kids) { + ComponentList c = components(e); + JComponent[] comps; + if (c == null) { + comps = e.getComponents(); + if (comps != null) { + all.addAll(Arrays.asList(comps)); + setComponents(e, c = new ComponentList(comps)); + } + } else { + boolean allStillAlive = c.addNotify(); + if (allStillAlive) { + comps = e.refresh(c.getComponents()); + ComponentList newComps; + if (comps != null && !((newComps = new ComponentList(comps)).equals(c))) { + setComponents(e, newComps); + c = newComps; + } + } else { + comps = e.findComponents(); + boolean fixed = c.repair(comps); + if (!fixed) { + setComponents(e, c = new ComponentList(comps)); + } + } + } + if (comps != null) { + for (JComponent comp : comps) { + expectHasIcon = ModelEntry.defaultHasIcon(comp, expectHasIcon); + } + } + if (c != null) { + all.addAll(Arrays.asList(c.getComponents())); + } + } + pruneSeparatorsAndFixIcons(all, expectHasIcon); + installComponents(all, old); +// long dur = System.currentTimeMillis() - start; +// System.err.println("Refresh " + getName() + " took " + dur + " milliseconds"); + return old; + } + + protected void installComponents(List components, CompType into) { + for (Component c : components) { + if (c == null) { + c = new JSeparator(); + } + into.add(c); + } + } + + private void pruneSeparatorsAndFixIcons(List all, Boolean icons) { + boolean lastWasSep = true; + for (Iterator i = all.iterator(); i.hasNext();) { + JComponent c = i.next(); + boolean isSep = c instanceof JSeparator; + if ((lastWasSep && isSep) || (!i.hasNext() && isSep)) { + i.remove(); + } + lastWasSep = isSep; + if (icons != null) { + ModelEntry.configureIcon(c, icons); + } + } + } + + private static enum Types { + + SEPARATOR(JSeparator.class), + DYNAMIC_MENU_CONTENT(DynamicMenuContent.class), + PRESENTER(Presenter.Menu.class), + MENU, + COMPONENT(Component.class), + ACTION(Action.class); + private final Class type; + + Types(Class type) { + this.type = type; + } + + Types() { + this(Object.class); + } + + boolean accept(DataObject dob) throws IOException, ClassNotFoundException { + if (this == MENU) { + DataFolder fld = dob instanceof DataFolder ? (DataFolder) dob + : dob.getLookup().lookup(DataFolder.class); + return fld != null; + } + InstanceCookie ck = dob.getLookup().lookup(InstanceCookie.class); + return ck instanceof InstanceCookie.Of ? ((InstanceCookie.Of) ck).instanceOf(type) + : ck == null ? false : type.isAssignableFrom(ck.instanceClass()); + } + + ModelEntry createEntry(DataObject ob, Callback callback) { +// System.err.println("Create " + name() + " entry for " + ob.getPrimaryFile().getPath()); + switch (this) { + case SEPARATOR: + return new SeparatorModelEntry(ob); + case ACTION: + InstanceCookie ic = ob.getLookup().lookup(InstanceCookie.class); + try { + //This is a bit of a mess - some InstanceCookies lie + if (ic.instanceCreate() instanceof Presenter.Menu) { + return new PresenterEntry(ob); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new ActionModelEntry(ob); + case DYNAMIC_MENU_CONTENT: + return new DynamicModelEntry(ob); + case PRESENTER: + return new PresenterEntry(ob); + case MENU: + DataFolder fld = ob instanceof DataFolder ? (DataFolder) ob + : ob.getLookup().lookup(DataFolder.class); + Parameters.notNull("folder was non-null but is now null", fld); + return new MenuEntry(fld, null); + case COMPONENT: + return new ComponentModelEntry(ob); + default: + throw new AssertionError(this + ""); + } + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.util.LinkedList; +import java.util.List; +import javax.swing.JMenuBar; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileRenameEvent; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +final class MenuBarModel extends FolderModelEntry { + private final JMenuBar bar; + private FCA fca = new FCA(); + private final RequestProcessor.Task task; + private static final int DELAY = 100; + MenuBarModel (DataFolder rootFolder, JMenuBar bar, RequestProcessor rp) { + super (rootFolder, JMenuBar.class); + this.bar = bar; + this.task = rp.create(fca); + rootFolder.getPrimaryFile().addFileChangeListener(fca); + } + + void init() { + fca.refresh(); + } + + @Override + protected JMenuBar findComponent() { + return bar; + } + + @Override + protected void onInitBackground(List> children) { + super.onInitBackground(children); + if (!refreshing) { + EventQueue.invokeLater(fca); + } + } + + volatile boolean refreshing; + private final class FCA extends FileChangeAdapter implements Runnable { + private volatile boolean enqueued; + boolean refresh() { + boolean result; + synchronized (this) { + result = enqueued; + if (!enqueued) { + enqueued = true; + task.schedule(DELAY); + } + return result; + } + } + + @Override + public void run() { + if (!EventQueue.isDispatchThread()) { + refreshing = true; + try { + List> l = new LinkedList>(); + initBackground(l); + setChildren(l); + } finally { + refreshing = false; + EventQueue.invokeLater(this); + } + } else { + enqueued = false; + bar.removeAll(); + MenuBarModel.this.refresh(new JMenuBar[] { bar }); + } + } + + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + refresh(); + } + + @Override + public void fileChanged(FileEvent fe) { + refresh(); + } + + @Override + public void fileDataCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileDeleted(FileEvent fe) { + refresh(); + } + + @Override + public void fileFolderCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileRenamed(FileRenameEvent fe) { + refresh(); + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,106 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import javax.swing.ButtonModel; +import javax.swing.JMenu; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.awt.Mnemonics; +import org.openide.loaders.DataFolder; + +/** + * + * @author Tim Boudreau + */ +final class MenuEntry extends FolderModelEntry implements ChangeListener { + + private Reference menu; + private final Callback callback; + + MenuEntry (DataFolder fld, Callback callback) { + super (fld, JMenu.class); + this.callback = callback == null ? this : callback; + } + + private JMenu menu() { + JMenu result = menu == null ? null : menu.get(); + if (result == null) { + result = new JMenu(); + Mnemonics.setLocalizedText(result, getName()); + result.getModel().addChangeListener(this); + menu = new WeakReference (result); + } + return result; + } + + @Override + protected JMenu findComponent() { + assert EventQueue.isDispatchThread(); + return menu(); + } + + private boolean wasSelected = false; + @Override + public void stateChanged(ChangeEvent e) { + ButtonModel mdl = (ButtonModel) e.getSource(); + boolean selected = mdl.isSelected(); + //XXX wasSelected could be true if the old component was garbage collected + //without resetting its model - possible? + if (selected && !wasSelected) { + if (callback != null) { + callback.onShow(this); + } + refreshComponent(menu()); + } else if (wasSelected && !selected) { + JMenu m = menu == null ? null : menu.get(); + if (m != null) { + m.removeAll(); + } + if (callback != null) { + callback.onHide(this); + } + } + wasSelected = selected; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,63 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import javax.swing.JMenuBar; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +final class MenuManager { + private final JMenuBar bar; + private final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); + private final MenuBarModel model; + + MenuManager(DataFolder folder, JMenuBar bar) { + this.bar = bar; + model = new MenuBarModel (folder, bar, rp); + } + + void init() { + model.init(); + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import javax.swing.JFrame; +import javax.swing.JMenuBar; +import org.netbeans.api.menus.MenuProvider; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataFolder; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Tim Boudreau + */ +@ServiceProvider(service=MenuProvider.class) +public final class MenuProviderImpl extends MenuProvider { + private static final String MENU_FOLDER = "Menu"; //NOI18N + private FileObject menuFolder; + public MenuProviderImpl () { + this (FileUtil.getConfigFile(MENU_FOLDER)); + } + + MenuProviderImpl (FileObject menuFolder) { //used in unit tests w/ memfs + this.menuFolder = menuFolder; + } + + @Override + protected JMenuBar createMenu(JFrame target) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException("Not on event thread"); //NOI18N + } + FileObject menuFO = menuFolder; + DataFolder menuFolder = DataFolder.findFolder(menuFO); + JMenuBar bar = new JMenuBar(); + bar.setBorderPainted(false); + MenuManager mgr = new MenuManager(menuFolder, bar); + mgr.init(); + return bar; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,172 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.lang.reflect.Array; +import java.util.List; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.loaders.DataObject; +import org.openide.util.ImageUtilities; +import org.openide.util.Parameters; + +/** + * + * @author Tim Boudreau + */ +public abstract class ModelEntry { + private final DataObjectType file; + protected final Class dobType; + private Class compType; + protected static final Icon BLANK_ICON = ImageUtilities.loadImageIcon("org/openide/loaders/empty.gif", false); // NOI18N + public ModelEntry(DataObjectType file, Class dobType, Class compType) { + this.file = file; + this.dobType = dobType; + this.compType = compType; + Parameters.notNull("compType", compType); + Parameters.notNull("dobType", dobType); + Parameters.notNull("file", file); + } + + protected abstract boolean initBackground(List> children); + + public String getName() { + return file().getNodeDelegate().getDisplayName(); + } + + protected final DataObjectType file() { + return file; + } + + final Class componentType() { + return compType; + } + + final CompType[] getComponents() { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); //NOI18N + } + return findComponents(); + } + + final CompType[] refreshComponents(CompType[] old) { + if (!EventQueue.isDispatchThread()) { + throw new IllegalStateException ("Not on event thread"); //NOI18N + } + return refresh (old); + } + + protected CompType[] refresh (CompType[] old) { + return toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + } + + protected CompType[] findComponents() { + return toCompTypeArray (new Object[] { findComponent() }); + } + + protected final CompType[] toCompTypeArray(CompType type) { + return toCompTypeArray (new Object[] { type }); + } + + protected final CompType[] toCompTypeArray(Object[] objs) { + CompType[] c = (CompType[]) Array.newInstance(compType, objs == null ? 0 : objs.length); + System.arraycopy(objs, 0, c, 0, objs.length); + return c; + } + + protected CompType findComponent() { + throw new UnsupportedOperationException("Override findComponent() " + //NOI18N + "or findComponents() in " + getClass().getName()); //NOI18N + } + + protected CompType refreshComponent(CompType old) { + throw new UnsupportedOperationException("Override refreshComponent() or " + //NOI18N + "refreshComponents() in " + getClass().getName()); //NOI18N + } + + protected static void configureIcon (JComponent comp, boolean wantIcons) { + if (comp instanceof JMenuItem) { + JMenuItem item = (JMenuItem) comp; + Icon icon = item.getIcon(); + if (!wantIcons && icon != null) { + item.setIcon(null); + } else if (wantIcons && icon == null) { + item.setIcon (BLANK_ICON); + } + } + } + + protected static Boolean defaultHasIcon (JComponent comp, Boolean hasIcon) { + if (hasIcon != null && hasIcon) { + return hasIcon; + } + if (comp instanceof JMenuItem) { + Icon icon = ((JMenuItem) comp).getIcon(); + if (icon != null && BLANK_ICON != icon) { + return true; + } + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ModelEntry other = (ModelEntry) obj; + if (this.file() != other.file() && (this.file() == null || !this.file().equals(other.file()))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 31 * hash + (this.file != null ? this.file.hashCode() : 0); + return hash; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/PresenterEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/PresenterEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,86 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.Component; +import java.io.IOException; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +public class PresenterEntry extends ThreadUnconstrainedModelEntry { + private DynamicMenuContent dmc; + PresenterEntry (DataObject dob) { + super (dob, JComponent.class, Presenter.Menu.class); + } + + @Override + protected JComponent[] findComponents() { + try { + Presenter.Menu p = super.get(); + JMenuItem c = p.getMenuPresenter(); + if (c instanceof DynamicMenuContent) { //ToolsAction.Inline + dmc = (DynamicMenuContent) c; + return dmc.getMenuPresenters(); + } + return new JComponent[] { c }; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return new JComponent[] { new JMenuItem("Error fetching " + file().getName()) }; //XXX + } + + @Override + protected JComponent[] refresh(JComponent[] old) { + if (dmc != null) { + return dmc.synchMenuPresenters(old); + } + return old; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.util.List; +import javax.swing.JSeparator; +import org.openide.loaders.DataObject; + +/** + * + * @author Tim Boudreau + */ +final class SeparatorModelEntry extends FileModelEntry { + SeparatorModelEntry(DataObject dob) { + super (dob, JSeparator.class, JSeparator.class); + } + + @Override + protected JSeparator findComponent() { + return new JSeparator(); + } + + @Override + protected JSeparator refreshComponent(JSeparator old) { + return old == null ? findComponent() : old; + } + + @Override + protected boolean initBackground(List> children) { + return false; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JComponent; +import org.openide.loaders.DataObject; +import org.openide.util.Exceptions; + +/** + * + * @author Tim Boudreau + */ +public abstract class ThreadUnconstrainedModelEntry extends FileModelEntry { + private final AtomicReference ref = new AtomicReference(); + + ThreadUnconstrainedModelEntry(DataObject dob, Class componentType, Class modelObjectType) { + super(dob, componentType, modelObjectType); + } + + @Override + protected final T get() throws IOException, ClassNotFoundException { + T result = ref.get(); + if (result == null) { + result = super.get(); + ref.set(result); + } + return result; + } + + @Override + protected boolean initBackground(List> children) { + //Since it is non-gui, instantiate the Action in the background + try { + ref.set(super.get()); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (ClassNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + return false; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/src/org/netbeans/core/menu/layer.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/layer.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,7 @@ + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/DynAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/DynAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,84 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.awt.DynamicMenuContent; + +/** + * + * @author Tim Boudreau + */ +public class DynAction implements DynamicMenuContent { + private static final int count = 5; + static int total; + final int index; + public DynAction() { + index = total++; + } + + @Override + public JComponent[] getMenuPresenters() { + JComponent[] result = new JComponent[count]; + for (int i= 0; i < result.length; i++) { + JMenuItem j = new JMenuItem ("Dynamic " + index + ":" + i); + j.putClientProperty("caption", j.getText()); + j.putClientProperty("syncCount", 0); + result[i] = j; + } + return result; + } + + @Override + public JComponent[] synchMenuPresenters(JComponent[] items) { + for (JComponent jc : items) { + JMenuItem m = (JMenuItem) jc; + String cap = (String) m.getClientProperty("caption"); + int syncs = ((Integer) m.getClientProperty("syncCount")).intValue(); + syncs ++; + m.putClientProperty("syncCount", syncs); + m.setText (cap + " (sync " + syncs + ")"); + } + return items; + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,203 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseEvent; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.swing.AbstractButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import org.junit.Test; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.XMLFileSystem; + +/** + * + * @author tim + */ +public class MenuProviderImplTest extends NbTestCase { + + private MenuProviderImpl m; + private JMenuBar bar; + private JFrame jf; + private JMenu menu; + + public MenuProviderImplTest(String name) { + super(name); + } + + @Override + protected boolean runInEQ() { + return false; + } + + @Override + public void setUp() throws Exception { + FileSystem fs = new XMLFileSystem(MenuProviderImplTest.class.getResource("fakelayer.xml")); + m = new MenuProviderImpl(fs.getRoot().getFileObject("Menu")); + R r = new R(); + + EventQueue.invokeAndWait(r); + r.latch.await(20000, TimeUnit.MILLISECONDS); + while (!jf.isShowing()) { + Thread.sleep(30); + } + assertFalse(bar.getMenuCount() == 0); + menu = bar.getMenu(0); + } + + @Override + public void tearDown() { + jf.dispose(); + } + + private void click(final JComponent comp, final boolean showMenu) throws Exception { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + ((AbstractButton) comp).getModel().setPressed(showMenu); + MouseEvent evt = new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + evt = new MouseEvent(comp, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + evt = new MouseEvent(comp, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + comp.dispatchEvent(evt); + } + }); + if (comp instanceof JMenu) { + JMenu m = (JMenu) comp; + while (showMenu != m.getPopupMenu().isShowing()) { + Thread.sleep (100); + } + } + } + + @Test + public void testMenuItemsAreNotStronglyReferenced() throws Exception { + assertNotNull (menu); + click (menu, true); + JPopupMenu popup = menu.getPopupMenu(); + Component[] c = popup.getComponents(); + assertEquals (7, c.length); + List> l = new ArrayList>(); + boolean presenterActionFound = false; + boolean regularActionFound = false; + int pActionIx = -1; + int rActionIx = -1; + for (int i=0; i < c.length; i++) { + Component cc = c[i]; + boolean wasPactionFound = presenterActionFound; + presenterActionFound |= cc instanceof PresenterAction.Marker; + if (!wasPactionFound && presenterActionFound) { + pActionIx = i; + } + l.add (new WeakReference(cc)); + boolean wasRactionFound = regularActionFound; + regularActionFound |= cc instanceof JMenuItem && ((JMenuItem) cc).getText().equals("Regular Action"); + if (!wasRactionFound && regularActionFound) { + rActionIx = i; + } + } + //Test order while we're here + assertTrue (presenterActionFound); + assertTrue (regularActionFound); + assertEquals ("Wrong index for presenter action", 5, pActionIx); + assertEquals ("Wrong index for presenter action", 6, rActionIx); + click (menu, false); + c = null; + for (Reference r : l) { + assertGC("Menu component not collected", r); + } + } + private static final int EXPECTED_MENU_COUNT = 1; //update if more added to xml + + private class R implements Runnable, ContainerListener { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void run() { + jf = new JFrame(); + bar = m.createMenu(jf); + bar.addContainerListener(this); + JPanel pnl = new JPanel(); + pnl.setMinimumSize(new Dimension(300, 300)); + pnl.setPreferredSize(new Dimension(300, 300)); + jf.setContentPane(pnl); + jf.setJMenuBar(bar); + jf.pack(); + jf.setVisible(true); + } + + @Override + public void componentAdded(ContainerEvent e) { + int count = e.getContainer().getComponentCount(); + if (count == EXPECTED_MENU_COUNT) { + latch.countDown(); + e.getContainer().invalidate(); + ((JMenuBar) e.getContainer()).revalidate(); + e.getContainer().repaint(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { + //do nothing + } + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/PresenterAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/PresenterAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.JMenuItem; +import org.openide.util.actions.Presenter; + +/** + * + * @author Tim Boudreau + */ +public class PresenterAction extends AbstractAction implements Presenter.Menu { + + @Override + public JMenuItem getMenuPresenter() { + return new Marker ("Menu Presenter"); + } + + @Override + public void actionPerformed(ActionEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public class Marker extends JMenuItem { + Marker (String s) { + super (s); + } + } + +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/RegularAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/RegularAction.java Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; + +/** + * + * @author Tim Boudreau + */ +public class RegularAction extends AbstractAction { + private boolean performed; + public RegularAction() { + putValue (NAME, "Regular Action"); + } + + @Override + public void actionPerformed(ActionEvent e) { + performed = true; + } +} diff -r 1f7b91420663 -r a60721f477e2 core.menu/test/unit/src/org/netbeans/core/menu/fakelayer.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/fakelayer.xml Sat Mar 27 04:32:30 2010 -0400 @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff -r 1f7b91420663 -r a60721f477e2 nbbuild/cluster.properties --- a/nbbuild/cluster.properties Fri Mar 26 13:27:09 2010 -0400 +++ b/nbbuild/cluster.properties Sat Mar 27 04:32:30 2010 -0400 @@ -184,6 +184,7 @@ nb.cluster.platform.depends= nb.cluster.platform=\ api.annotations.common,\ + api.menus,\ api.progress,\ api.visual,\ applemenu,\ @@ -192,6 +193,7 @@ core.execution,\ core.io.ui,\ core.kit,\ + core.menu,\ core.multiview,\ core.nativeaccess,\ core.netigso,\ # HG changeset patch # User tboudreau@netbeans.org # Date 1269678969 14400 # Branch menus-rewrite-182698 # Node ID fa24874d2b7b2b28c14edef12c11d4300074add5 # Parent a60721f477e2c24c94a57db36e4563a2c870cc7d #182698 - main window to use new API, not MenuBar diff -r a60721f477e2 -r fa24874d2b7b core.windows/manifest.mf --- a/core.windows/manifest.mf Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/manifest.mf Sat Mar 27 04:36:09 2010 -0400 @@ -6,5 +6,5 @@ OpenIDE-Module-Recommends: org.netbeans.core.windows.nativeaccess.NativeWindowSystem AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 2.20 +OpenIDE-Module-Specification-Version: 2.21 diff -r a60721f477e2 -r fa24874d2b7b core.windows/nbproject/project.xml --- a/core.windows/nbproject/project.xml Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/nbproject/project.xml Sat Mar 27 04:36:09 2010 -0400 @@ -47,6 +47,15 @@ org.netbeans.core.windows + org.netbeans.api.menus + + + + 1 + 1.0 + + + org.netbeans.core diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java --- a/core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/WindowSystemImpl.java Sat Mar 27 04:36:09 2010 -0400 @@ -45,9 +45,7 @@ import java.awt.EventQueue; import org.netbeans.core.WindowSystem; -import org.netbeans.core.windows.persistence.PersistenceManager; import org.netbeans.core.windows.services.DialogDisplayerImpl; -import org.netbeans.core.windows.view.ui.MainWindow; import org.openide.util.lookup.ServiceProvider; @@ -61,7 +59,6 @@ public void init() { assert !EventQueue.isDispatchThread(); - MainWindow.init(); } public void load() { diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java --- a/core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ViewHierarchy.java Sat Mar 27 04:36:09 2010 -0400 @@ -122,6 +122,7 @@ public MainWindow getMainWindow() { if (mainWindow == null) { mainWindow = new MainWindow(); + mainWindow.init(); } return mainWindow; } diff -r a60721f477e2 -r fa24874d2b7b core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sat Mar 27 04:32:30 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sat Mar 27 04:36:09 2010 -0400 @@ -62,6 +62,7 @@ import javax.swing.JPanel; import javax.swing.border.*; import javax.swing.event.*; +import org.netbeans.api.menus.MenuProvider; import org.netbeans.core.windows.*; import org.netbeans.core.windows.view.ui.toolbars.ToolbarConfiguration; import org.openide.LifecycleManager; @@ -138,7 +139,7 @@ } } - public static void init() { + public void init() { if (mainMenuBar == null) { mainMenuBar = createMenuBar(); ToolbarPool.getDefault().waitFinished(); @@ -365,8 +366,12 @@ ); } + private JMenuBar createMenuBar() { + return MenuProvider.installMenu(this); + } + /** Creates menu bar. */ - private static JMenuBar createMenuBar() { + private static JMenuBar xcreateMenuBar() { JMenuBar menu = getCustomMenuBar(); if (menu == null) { menu = new MenuBar (null); # HG changeset patch # User tboudreau@netbeans.org # Date 1269681128 14400 # Branch menus-rewrite-182698 # Node ID d76d479b5dc8583f8edb2c259b1b6dbd9d58ccb1 # Parent fa24874d2b7b2b28c14edef12c11d4300074add5 Move folder listening logic to shared superclass for menus and menu bars diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -52,6 +52,7 @@ import java.util.Set; import javax.swing.Action; import javax.swing.JComponent; +import javax.swing.JMenuItem; import javax.swing.JSeparator; import org.openide.awt.DynamicMenuContent; import org.openide.cookies.InstanceCookie; @@ -267,6 +268,9 @@ if (icons != null) { ModelEntry.configureIcon(c, icons); } + if (c instanceof JMenuItem) { + c.setToolTipText(null); + } } } @@ -299,7 +303,7 @@ : ck == null ? false : type.isAssignableFrom(ck.instanceClass()); } - ModelEntry createEntry(DataObject ob, Callback callback) { + ModelEntry createEntry(DataObject ob, FolderModelEntry entry) { // System.err.println("Create " + name() + " entry for " + ob.getPrimaryFile().getPath()); switch (this) { case SEPARATOR: @@ -325,7 +329,7 @@ DataFolder fld = ob instanceof DataFolder ? (DataFolder) ob : ob.getLookup().lookup(DataFolder.class); Parameters.notNull("folder was non-null but is now null", fld); - return new MenuEntry(fld, null); + return new MenuEntry(fld, MenuManager.rp); case COMPONENT: return new ComponentModelEntry(ob); default: diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- a/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 05:12:08 2010 -0400 @@ -39,14 +39,7 @@ package org.netbeans.core.menu; -import java.awt.EventQueue; -import java.util.LinkedList; -import java.util.List; import javax.swing.JMenuBar; -import org.openide.filesystems.FileAttributeEvent; -import org.openide.filesystems.FileChangeAdapter; -import org.openide.filesystems.FileEvent; -import org.openide.filesystems.FileRenameEvent; import org.openide.loaders.DataFolder; import org.openide.util.RequestProcessor; @@ -54,20 +47,11 @@ * * @author Tim Boudreau */ -final class MenuBarModel extends FolderModelEntry { +final class MenuBarModel extends RefreshingFolderModelEntry { private final JMenuBar bar; - private FCA fca = new FCA(); - private final RequestProcessor.Task task; - private static final int DELAY = 100; MenuBarModel (DataFolder rootFolder, JMenuBar bar, RequestProcessor rp) { - super (rootFolder, JMenuBar.class); + super (rootFolder, JMenuBar.class, rp); this.bar = bar; - this.task = rp.create(fca); - rootFolder.getPrimaryFile().addFileChangeListener(fca); - } - - void init() { - fca.refresh(); } @Override @@ -76,75 +60,7 @@ } @Override - protected void onInitBackground(List> children) { - super.onInitBackground(children); - if (!refreshing) { - EventQueue.invokeLater(fca); - } - } - - volatile boolean refreshing; - private final class FCA extends FileChangeAdapter implements Runnable { - private volatile boolean enqueued; - boolean refresh() { - boolean result; - synchronized (this) { - result = enqueued; - if (!enqueued) { - enqueued = true; - task.schedule(DELAY); - } - return result; - } - } - - @Override - public void run() { - if (!EventQueue.isDispatchThread()) { - refreshing = true; - try { - List> l = new LinkedList>(); - initBackground(l); - setChildren(l); - } finally { - refreshing = false; - EventQueue.invokeLater(this); - } - } else { - enqueued = false; - bar.removeAll(); - MenuBarModel.this.refresh(new JMenuBar[] { bar }); - } - } - - @Override - public void fileAttributeChanged(FileAttributeEvent fe) { - refresh(); - } - - @Override - public void fileChanged(FileEvent fe) { - refresh(); - } - - @Override - public void fileDataCreated(FileEvent fe) { - refresh(); - } - - @Override - public void fileDeleted(FileEvent fe) { - refresh(); - } - - @Override - public void fileFolderCreated(FileEvent fe) { - refresh(); - } - - @Override - public void fileRenamed(FileRenameEvent fe) { - refresh(); - } + JMenuBar cachedComponent() { + return bar; } } diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- a/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -42,25 +42,23 @@ import java.awt.EventQueue; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.ButtonModel; import javax.swing.JMenu; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openide.awt.Mnemonics; import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; /** * * @author Tim Boudreau */ -final class MenuEntry extends FolderModelEntry implements ChangeListener { - +final class MenuEntry extends RefreshingFolderModelEntry implements ChangeListener { private Reference menu; - private final Callback callback; - - MenuEntry (DataFolder fld, Callback callback) { - super (fld, JMenu.class); - this.callback = callback == null ? this : callback; + MenuEntry (DataFolder fld, RequestProcessor rp) { + super (fld, JMenu.class, rp); } private JMenu menu() { @@ -81,6 +79,7 @@ } private boolean wasSelected = false; + private AtomicBoolean showing = new AtomicBoolean(); @Override public void stateChanged(ChangeEvent e) { ButtonModel mdl = (ButtonModel) e.getSource(); @@ -88,19 +87,22 @@ //XXX wasSelected could be true if the old component was garbage collected //without resetting its model - possible? if (selected && !wasSelected) { - if (callback != null) { - callback.onShow(this); - } + showing.set(true); + onShow(this); refreshComponent(menu()); } else if (wasSelected && !selected) { + showing.set(false); JMenu m = menu == null ? null : menu.get(); if (m != null) { m.removeAll(); } - if (callback != null) { - callback.onHide(this); - } + onHide(this); } wasSelected = selected; } + + @Override + JMenu cachedComponent() { + return menu(); + } } diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/MenuManager.java --- a/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 04:36:09 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuManager.java Sat Mar 27 05:12:08 2010 -0400 @@ -49,7 +49,8 @@ */ final class MenuManager { private final JMenuBar bar; - private final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); + //XXX make this non-static and pass to model entries + static final RequestProcessor rp = new RequestProcessor("Main Menu Refresh", 1, true); private final MenuBarModel model; MenuManager(DataFolder folder, JMenuBar bar) { diff -r fa24874d2b7b -r d76d479b5dc8 core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 05:12:08 2010 -0400 @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.EventQueue; +import java.util.LinkedList; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JMenuBar; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataFolder; +import org.openide.util.RequestProcessor; + +/** + * + * @author Tim Boudreau + */ +abstract class RefreshingFolderModelEntry extends FolderModelEntry { + + private final RequestProcessor.Task task; + private FCA fca = new FCA(); + private static final int DELAY = 100; + + RefreshingFolderModelEntry(DataFolder dob, Class compType, RequestProcessor rp) { + super(dob, compType); + this.task = rp.create(fca); + FileObject fo = dob.getPrimaryFile(); + fo.addFileChangeListener(FileUtil.weakFileChangeListener(fca, fo)); + } + + @Override + protected void onInitBackground(List> children) { + super.onInitBackground(children); + if (!refreshing) { + EventQueue.invokeLater(fca); + } + } + + abstract T cachedComponent(); + + void init() { + fca.refresh(); + } + volatile boolean refreshing; + + private final class FCA extends FileChangeAdapter implements Runnable { + + private volatile boolean enqueued; + + boolean refresh() { + boolean result; + synchronized (this) { + result = enqueued; + if (!enqueued) { + enqueued = true; + task.schedule(DELAY); + } + return result; + } + } + + @Override + public void run() { + if (!EventQueue.isDispatchThread()) { + refreshing = true; + try { + List> l = new LinkedList>(); + initBackground(l); + setChildren(l); + } finally { + refreshing = false; + EventQueue.invokeLater(this); + } + } else { + enqueued = false; + T comp = cachedComponent(); + if (comp != null) { + comp.removeAll(); + RefreshingFolderModelEntry.this.refresh(toCompTypeArray(new Object[]{comp})); + } + } + } + + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + refresh(); + } + + @Override + public void fileChanged(FileEvent fe) { + refresh(); + } + + @Override + public void fileDataCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileDeleted(FileEvent fe) { + refresh(); + } + + @Override + public void fileFolderCreated(FileEvent fe) { + refresh(); + } + + @Override + public void fileRenamed(FileRenameEvent fe) { + refresh(); + } + } +} # HG changeset patch # User tboudreau@netbeans.org # Date 1269723437 14400 # Branch menus-rewrite-182698 # Node ID b3556c2449f0164ac1628e8398bea7000f29168e # Parent d76d479b5dc8583f8edb2c259b1b6dbd9d58ccb1 Tests to ensure file changes are noticed and open popup menus are updated diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/src/org/netbeans/core/menu/MenuEntry.java --- a/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 05:12:08 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuEntry.java Sat Mar 27 16:57:17 2010 -0400 @@ -78,6 +78,20 @@ return menu(); } + @Override + protected void onRefresh(JMenu[] newComps) { + if (showing.get()) { + JMenu m = menu(); + if (m != null && m.isPopupMenuVisible()) { + m.setPopupMenuVisible(false); + m.invalidate(); + m.revalidate(); + m.repaint(); + m.setPopupMenuVisible(true); + } + } + } + private boolean wasSelected = false; private AtomicBoolean showing = new AtomicBoolean(); @Override diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/src/org/netbeans/core/menu/ModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 05:12:08 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 16:57:17 2010 -0400 @@ -70,7 +70,8 @@ protected abstract boolean initBackground(List> children); public String getName() { - return file().getNodeDelegate().getDisplayName(); + DataObject file = file(); + return file.isValid() ? file.getNodeDelegate().getDisplayName() : file.getName(); } protected final DataObjectType file() { @@ -95,8 +96,14 @@ return refresh (old); } + protected void onRefresh (CompType[] newComps) { + + } + protected CompType[] refresh (CompType[] old) { - return toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + CompType[] result = toCompTypeArray(new Object[] { refreshComponent( old.length == 0 ? null : old[0]) }); + onRefresh (result); + return result; } protected CompType[] findComponents() { diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/AAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/AAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class AAction extends AbstractAction { + public AAction() { + putValue (NAME, "A"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABAction extends AbstractAction { + public ABAction() { + putValue (NAME, "AB"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABCAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABCAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABCAction extends AbstractAction { + public ABCAction() { + putValue (NAME, "ABC"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B, Layers.C); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/ABCDAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/ABCDAction.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.netbeans.core.menu.FileChangeTest.Layers; +import org.openide.util.Exceptions; +import org.xml.sax.SAXException; + +/** + * + * @author Tim Boudreau + */ +public class ABCDAction extends AbstractAction { + public ABCDAction() { + putValue (NAME, "ABCD"); + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + FileChangeTest.MFS.INSTANCE.setLayers(Layers.A, Layers.B, Layers.C, Layers.D); + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } + } + + + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/EventBlocker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/EventBlocker.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,96 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.core.menu; + +import java.awt.AWTEvent; +import static java.awt.AWTEvent.*; +import java.awt.Component; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.openide.util.Exceptions; + +/** + * Blocks real input events from the user, so user's focus behavior cannot + * affect tests + * + * @author Tim Boudreau + */ +public class EventBlocker implements AWTEventListener { + private static final long MASK = FOCUS_EVENT_MASK | MOUSE_EVENT_MASK | MOUSE_MOTION_EVENT_MASK | MOUSE_WHEEL_EVENT_MASK | + KEY_EVENT_MASK | ACTION_EVENT_MASK | ADJUSTMENT_EVENT_MASK | + INPUT_METHOD_EVENT_MASK | ITEM_EVENT_MASK | TEXT_EVENT_MASK; + static void init() { + Toolkit.getDefaultToolkit().addAWTEventListener(new EventBlocker(), MASK); + } + + @Override + public void eventDispatched(AWTEvent event) { + Class c = event.getClass(); + if (event instanceof ME) { + return; + } + try { + Method m = c.getMethod("consume"); + try { + m.invoke(event); + } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException ex) { + } catch (InvocationTargetException ex) { + } + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } + } + + public static MouseEvent mouseEvent (Component c, int id) { + return new ME(c, id); + } + + private static final class ME extends MouseEvent { + ME(Component comp, int id) { + super (comp, id, System.currentTimeMillis(), + 0, 5, 5, 1, false, 0); + } + } + +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/FileChangeTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/FileChangeTest.java Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,303 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ +package org.netbeans.core.menu; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseEvent; +import java.net.URL; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import javax.swing.AbstractButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import org.junit.Test; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.MultiFileSystem; +import org.openide.filesystems.XMLFileSystem; +import org.xml.sax.SAXException; + +/** + * + * @author tim + */ +public class FileChangeTest extends NbTestCase { + + private MenuProviderImpl m; + private JMenuBar bar; + private JFrame jf; + private JMenu menu; + + + public FileChangeTest(String name) { + super(name); + } + + @Override + protected boolean runInEQ() { + return false; + } + + @Override + public void setUp() throws Exception { + FileSystem fs = new MFS(); + m = new MenuProviderImpl(fs.getRoot().getFileObject("Menu")); + R r = new R(); + + EventQueue.invokeAndWait(r); + r.latch.await(20000, TimeUnit.MILLISECONDS); + while (!jf.isShowing()) { + Thread.sleep(30); + } + while (bar.getMenuCount() < 2) { + Thread.sleep(30); + } + assertFalse(bar.getMenuCount() == 0); + menu = bar.getMenu(0); + } + + @Override + public void tearDown() { + jf.dispose(); + } + + private void click(final JComponent comp, final boolean showMenu) throws Exception { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + ((AbstractButton) comp).getModel().setPressed(showMenu); + MouseEvent evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_PRESSED); + comp.dispatchEvent(evt); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_RELEASED); + comp.dispatchEvent(evt); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_CLICKED); + comp.dispatchEvent(evt); + } + }); + if (comp instanceof JMenu) { + JMenu m = (JMenu) comp; + while (showMenu != m.getPopupMenu().isShowing()) { + Thread.sleep (100); + } + } + } + + + @Test + public void testMenusAreUpdatedWhenOpen() throws Exception { + EventBlocker.init(); + assertNotNull (menu); + click (menu, true); + JPopupMenu popup = menu.getPopupMenu(); + Component[] c = popup.getComponents(); + assertTrue (menu.isPopupMenuVisible()); + assertEquals (4, popup.getComponentCount()); + final CountDownLatch l = new CountDownLatch(2); + popup.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("added - count now " + e.getContainer().getComponentCount()); + if (e.getContainer().getComponentCount() == 5) { + l.countDown(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("removed - count now " + e.getContainer().getComponentCount()); + } + + }); + bar.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("bar comp added - count now " + e.getContainer().getComponentCount()); + if (e.getContainer().getComponentCount() == 3) { + l.countDown(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("bar comp removed - count now " + e.getContainer().getComponentCount()); + } + + }); + + new ABCDAction().actionPerformed(null); + l.await(20000, TimeUnit.MILLISECONDS); + final AtomicInteger popupCompCount = new AtomicInteger(-1); + final AtomicInteger barCompCount = new AtomicInteger(-1); + final AtomicBoolean vis = new AtomicBoolean(); + EventQueue.invokeAndWait (new Runnable() { + + @Override + public void run() { + popupCompCount.set(menu.getPopupMenu().getComponentCount()); + barCompCount.set(bar.getComponentCount()); + vis.set(menu.getPopupMenu().isShowing()); + } + + }); + + assertTrue (vis.get()); + assertEquals (5, popupCompCount.get()); + assertEquals (3, barCompCount.get()); + + popupCompCount.set(-1); + barCompCount.set(-1); + vis.set(false); + + final CountDownLatch ll = new CountDownLatch(4); + popup.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(ContainerEvent e) { +// System.err.println("added - count now " + e.getContainer().getComponentCount()); + ll.countDown(); + } + + @Override + public void componentRemoved(ContainerEvent e) { +// System.err.println("removed - count now " + e.getContainer().getComponentCount()); + } + + }); + new AAction().actionPerformed(null); + ll.await(20000, TimeUnit.MILLISECONDS); + EventQueue.invokeAndWait (new Runnable() { + + @Override + public void run() { + popupCompCount.set(menu.getPopupMenu().getComponentCount()); + barCompCount.set(bar.getComponentCount()); + vis.set(menu.getPopupMenu().isShowing()); + } + + }); + + assertTrue (vis.get()); + assertEquals (4, popupCompCount.get()); + assertEquals (2, barCompCount.get()); + +// Thread.sleep (40000); + + } + private static final int EXPECTED_MENU_COUNT = 1; //update if more added to xml + + private class R implements Runnable, ContainerListener { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void run() { + jf = new JFrame(); + bar = m.createMenu(jf); + bar.addContainerListener(this); + JPanel pnl = new JPanel(); + pnl.setMinimumSize(new Dimension(300, 300)); + pnl.setPreferredSize(new Dimension(300, 300)); + jf.setContentPane(pnl); + jf.setJMenuBar(bar); + jf.pack(); + jf.setVisible(true); + } + + @Override + public void componentAdded(ContainerEvent e) { + int count = e.getContainer().getComponentCount(); + if (count == EXPECTED_MENU_COUNT) { + latch.countDown(); + e.getContainer().invalidate(); + ((JMenuBar) e.getContainer()).revalidate(); + e.getContainer().repaint(); + } + } + + @Override + public void componentRemoved(ContainerEvent e) { + //do nothing + } + } + + public static final class MFS extends MultiFileSystem { + public static MFS INSTANCE; + MFS() throws SAXException { + setLayers (Layers.A); + INSTANCE = this; + } + + public void setLayers(Layers... l) throws SAXException { + System.err.println("setLayers " + Arrays.asList(l)); + FileSystem[] xfs = new XMLFileSystem[l.length]; + for (int i = 0; i < xfs.length; i++) { + xfs[i] = new XMLFileSystem(l[i].res()); + } + setDelegates(xfs); + } + } + + public enum Layers { + A("a.xml"), + B("b.xml"), + C("c.xml"), + D("d.xml"); + private String resource; + Layers (String resource) { + this.resource = resource; + } + + public URL res() { + return Layers.class.getResource(resource); + } + } +} diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/a.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/a.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/b.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/b.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/c.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/c.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff -r d76d479b5dc8 -r b3556c2449f0 core.menu/test/unit/src/org/netbeans/core/menu/d.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/d.xml Sat Mar 27 16:57:17 2010 -0400 @@ -0,0 +1,12 @@ + + + + + + + + + + + + # HG changeset patch # User tboudreau@netbeans.org # Date 1269725476 14400 # Branch menus-rewrite-182698 # Node ID dd5ee1067e5247027b0535bdc9d6584e4df0f2c3 # Parent b3556c2449f0164ac1628e8398bea7000f29168e Get rid of menu warm-up, which is no longer needed. Eliminate unnecessary refresh of initial dynamic content. diff -r b3556c2449f0 -r dd5ee1067e52 core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 16:57:17 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sat Mar 27 17:31:16 2010 -0400 @@ -60,8 +60,6 @@ try { DynamicMenuContent dyn = get(); JComponent[] result = dyn.getMenuPresenters(); - //Necessary the first time, or we get bogus elements - dyn.synchMenuPresenters(result); return result; } catch (IOException ex) { Exceptions.printStackTrace(ex); diff -r b3556c2449f0 -r dd5ee1067e52 core.ui/manifest.mf --- a/core.ui/manifest.mf Sat Mar 27 16:57:17 2010 -0400 +++ b/core.ui/manifest.mf Sat Mar 27 17:31:16 2010 -0400 @@ -4,5 +4,5 @@ OpenIDE-Module-Layer: org/netbeans/core/ui/resources/layer.xml AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 1.21 +OpenIDE-Module-Specification-Version: 1.22 diff -r b3556c2449f0 -r dd5ee1067e52 core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java --- a/core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java Sat Mar 27 16:57:17 2010 -0400 +++ b/core.ui/src/org/netbeans/core/ui/warmup/MenuWarmUpTask.java Sat Mar 27 17:31:16 2010 -0400 @@ -45,6 +45,7 @@ import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; @@ -58,7 +59,6 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; -import javax.swing.SwingUtilities; import javax.swing.text.html.HTMLEditorKit; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; @@ -74,58 +74,24 @@ import org.openide.util.NbBundle; /** - * A menu preheating task. It is referenced from the layer and may be performed - * by the core after the startup. - * - * Plus hooked WindowListener on main window (see {@link NbWindowsAdapter}) + * Triggers filesystem refresh on main window activation. */ public final class MenuWarmUpTask implements Runnable { - private Component[] comps; - - /** Actually performs pre-heat. - */ @Override public void run() { try { - SwingUtilities.invokeAndWait(new Runnable() { + EventQueue.invokeLater(new Runnable() { @Override public void run() { Frame main = WindowManager.getDefault().getMainWindow(); - assert main != null; main.addWindowListener(new NbWindowsAdapter()); - - if (main instanceof JFrame) { - comps = ((JFrame) main).getJMenuBar().getComponents(); - } } }); } catch (Exception e) { // bail out! return; } - - - if (comps != null) { - walkMenu(comps); - comps = null; - } - - // tackle the Tools menu now? How? - } - - private void walkMenu(Component[] items) { - for (int i=0; i cls = items[i].getClass(); - Method m = cls.getDeclaredMethod("doInitialize"); - m.setAccessible(true); - m.invoke(items[i]); - walkMenu(((JMenu)items[i]).getMenuComponents()); // recursive? - } catch (Exception e) {// do nothing, it may happen for user-provided menus - } - } } /** # HG changeset patch # User tboudreau@netbeans.org # Date 1269728975 14400 # Branch menus-rewrite-182698 # Node ID 7a1c5c06f75b452207dd55626a65909e51f2bab3 # Parent dd5ee1067e5247027b0535bdc9d6584e4df0f2c3 Avoid unnecessary submenu creation on startup. Move entire menu init to after main window is shown (may not be viable on slow systems, but worth trying). MenuProviderTest now uses user-initiated AWT event blocking to avoid user-interference with test passing. diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -57,7 +57,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { return false; } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -142,12 +142,12 @@ } } - protected void onInitBackground(List> children) { + protected void onInitBackground(List> children, int currDepth) { //do nothing - for subclasses to force init } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { // System.err.println("initBackground " + getName()); DataFolder fld = file(); boolean changed = true; @@ -165,7 +165,7 @@ children.add(e); //XXX defer or batch afterward? List> kids = new ArrayList>(); - boolean hasKids = e.initBackground(kids); + boolean hasKids = e.initBackground(kids, currDepth+1); changed |= hasKids; if (hasKids) { if (e instanceof FolderModelEntry) { @@ -185,7 +185,7 @@ } } } finally { - onInitBackground(children); + onInitBackground(children, currDepth); } return changed; } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/MenuBarModel.java --- a/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuBarModel.java Sat Mar 27 18:29:35 2010 -0400 @@ -39,6 +39,8 @@ package org.netbeans.core.menu; +import java.util.List; +import javax.swing.JComponent; import javax.swing.JMenuBar; import org.openide.loaders.DataFolder; import org.openide.util.RequestProcessor; @@ -63,4 +65,18 @@ JMenuBar cachedComponent() { return bar; } + + @Override + protected void installComponents(List components, JMenuBar into) { + synchronized (bar.getTreeLock()) { + super.installComponents(components, into); + } + bar.invalidate(); + bar.revalidate(); + bar.repaint(); + } + + + + } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java --- a/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 18:29:35 2010 -0400 @@ -40,6 +40,10 @@ package org.netbeans.core.menu; import java.awt.EventQueue; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; import javax.swing.JFrame; import javax.swing.JMenuBar; import org.netbeans.api.menus.MenuProvider; @@ -71,11 +75,20 @@ } FileObject menuFO = menuFolder; DataFolder menuFolder = DataFolder.findFolder(menuFO); - JMenuBar bar = new JMenuBar(); + final JMenuBar bar = new JMenuBar(); bar.setBorderPainted(false); - MenuManager mgr = new MenuManager(menuFolder, bar); - mgr.init(); + final MenuManager mgr = new MenuManager(menuFolder, bar); + bar.addHierarchyListener(new HierarchyListener() { + + @Override + public void hierarchyChanged(HierarchyEvent e) { + if (bar.isShowing()) { + bar.removeHierarchyListener(this); + mgr.init(); + } + } + }); +// mgr.init(); return bar; } - } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -67,7 +67,7 @@ Parameters.notNull("file", file); } - protected abstract boolean initBackground(List> children); + protected abstract boolean initBackground(List> children, int currDepth); public String getName() { DataObject file = file(); @@ -136,12 +136,41 @@ Icon icon = item.getIcon(); if (!wantIcons && icon != null) { item.setIcon(null); + item.setDisabledIcon(null); + item.setDisabledSelectedIcon(null); } else if (wantIcons && icon == null) { item.setIcon (BLANK_ICON); + item.setDisabledIcon(BLANK_ICON); + item.setDisabledSelectedIcon(BLANK_ICON); } +// String txt = item.getText(); +// if (txt != null && txt.contains("Find Issues")) { +// Thread.dumpStack(); +// log (item); +// } } } +//Useful for diagnosing strangely painting menu items +// private static void log (JMenuItem item) { +// System.err.println("FIND ISSUES ITEM: " + item); +// logType (item); +// System.err.println(" Action:"); +// logType (item.getAction()); +// } +// +// private static void logType (Object o) { +// if (o == null) return; +// for (Class c : o.getClass().getInterfaces()) { +// System.err.println(" I:" + c.getName()); +// } +// Class x = o.getClass(); +// while (x != Object.class) { +// System.err.println(" C: " + x.getName()); +// x = x.getSuperclass(); +// } +// } + protected static Boolean defaultHasIcon (JComponent comp, Boolean hasIcon) { if (hasIcon != null && hasIcon) { return hasIcon; @@ -176,4 +205,9 @@ hash = 31 * hash + (this.file != null ? this.file.hashCode() : 0); return hash; } + + @Override + public String toString() { + return super.toString() + "[" + getName() + "]"; + } } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/RefreshingFolderModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -42,7 +42,6 @@ import java.util.LinkedList; import java.util.List; import javax.swing.JComponent; -import javax.swing.JMenuBar; import org.openide.filesystems.FileAttributeEvent; import org.openide.filesystems.FileChangeAdapter; import org.openide.filesystems.FileEvent; @@ -61,6 +60,7 @@ private final RequestProcessor.Task task; private FCA fca = new FCA(); private static final int DELAY = 100; + private static final boolean LOG_RUNNABLES = Boolean.getBoolean ("RefreshingFolderModelEntry.log"); RefreshingFolderModelEntry(DataFolder dob, Class compType, RequestProcessor rp) { super(dob, compType); @@ -70,10 +70,10 @@ } @Override - protected void onInitBackground(List> children) { - super.onInitBackground(children); - if (!refreshing) { - EventQueue.invokeLater(fca); + protected void onInitBackground(List> children, int currDepth) { + super.onInitBackground(children, currDepth); + if (!refreshing && currDepth == 0) { + EventQueue.invokeLater(LOG_RUNNABLES ? new LogRunnable(fca) : fca); } } @@ -84,6 +84,21 @@ } volatile boolean refreshing; + private final class LogRunnable implements Runnable { + Throwable t; + private final Runnable real; + LogRunnable (Runnable real) { + this.real = real; + t = new Exception("Refresh " + RefreshingFolderModelEntry.this).fillInStackTrace(); + } + + @Override + public void run() { + t.printStackTrace(); + real.run(); + } + } + private final class FCA extends FileChangeAdapter implements Runnable { private volatile boolean enqueued; @@ -94,7 +109,12 @@ result = enqueued; if (!enqueued) { enqueued = true; - task.schedule(DELAY); + if (!LOG_RUNNABLES) { + task.schedule(DELAY); + } else { + RequestProcessor.Task t = MenuManager.rp.create(new LogRunnable(this)); + t.schedule(DELAY); + } } return result; } @@ -106,11 +126,11 @@ refreshing = true; try { List> l = new LinkedList>(); - initBackground(l); + initBackground(l, 0); setChildren(l); } finally { refreshing = false; - EventQueue.invokeLater(this); + EventQueue.invokeLater(LOG_RUNNABLES ? new LogRunnable(this) : this); } } else { enqueued = false; diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -63,7 +63,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { return false; } } diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ThreadUnconstrainedModelEntry.java Sat Mar 27 18:29:35 2010 -0400 @@ -68,7 +68,7 @@ } @Override - protected boolean initBackground(List> children) { + protected boolean initBackground(List> children, int currDepth) { //Since it is non-gui, instantiate the Action in the background try { ref.set(super.get()); diff -r dd5ee1067e52 -r 7a1c5c06f75b core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java --- a/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 17:31:16 2010 -0400 +++ b/core.menu/test/unit/src/org/netbeans/core/menu/MenuProviderImplTest.java Sat Mar 27 18:29:35 2010 -0400 @@ -108,14 +108,11 @@ public void run() { ((AbstractButton) comp).getModel().setPressed(showMenu); - MouseEvent evt = new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + MouseEvent evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_PRESSED); comp.dispatchEvent(evt); - evt = new MouseEvent(comp, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_RELEASED); comp.dispatchEvent(evt); - evt = new MouseEvent(comp, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), - 0, 5, 5, 1, false, 0); + evt = EventBlocker.mouseEvent(comp, MouseEvent.MOUSE_CLICKED); comp.dispatchEvent(evt); } }); @@ -129,6 +126,7 @@ @Test public void testMenuItemsAreNotStronglyReferenced() throws Exception { + EventBlocker.init(); assertNotNull (menu); click (menu, true); JPopupMenu popup = menu.getPopupMenu(); # HG changeset patch # User tboudreau@netbeans.org # Date 1269752030 14400 # Branch menus-rewrite-182698 # Node ID fdb5086bb983b03d5333335050051a1e0d778855 # Parent 7a1c5c06f75b452207dd55626a65909e51f2bab3 Update docs, allow alternate path via sysprop, deprecate org.openide.awt.MenuBar diff -r 7a1c5c06f75b -r fdb5086bb983 core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java --- a/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sat Mar 27 18:29:35 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/MenuProviderImpl.java Sun Mar 28 00:53:50 2010 -0400 @@ -50,6 +50,7 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataFolder; +import org.openide.util.Parameters; import org.openide.util.lookup.ServiceProvider; /** @@ -61,23 +62,33 @@ private static final String MENU_FOLDER = "Menu"; //NOI18N private FileObject menuFolder; public MenuProviderImpl () { - this (FileUtil.getConfigFile(MENU_FOLDER)); + this (defaultMenuFolder()); } MenuProviderImpl (FileObject menuFolder) { //used in unit tests w/ memfs + Parameters.notNull("menuFolder", menuFolder); this.menuFolder = menuFolder; } + static FileObject defaultMenuFolder() { + String fld = System.getProperty ("MenuProviderImpl.menuBarFolder"); //NOI18N + if (fld == null) { + fld = MENU_FOLDER; + } + return FileUtil.getConfigFile(fld); + } + @Override protected JMenuBar createMenu(JFrame target) { if (!EventQueue.isDispatchThread()) { throw new IllegalStateException("Not on event thread"); //NOI18N } + Parameters.notNull("target", target); //NOI18N FileObject menuFO = menuFolder; - DataFolder menuFolder = DataFolder.findFolder(menuFO); + DataFolder folder = DataFolder.findFolder(menuFO); final JMenuBar bar = new JMenuBar(); bar.setBorderPainted(false); - final MenuManager mgr = new MenuManager(menuFolder, bar); + final MenuManager mgr = new MenuManager(folder, bar); bar.addHierarchyListener(new HierarchyListener() { @Override @@ -88,7 +99,6 @@ } } }); -// mgr.init(); return bar; } } diff -r 7a1c5c06f75b -r fdb5086bb983 core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sat Mar 27 18:29:35 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sun Mar 28 00:53:50 2010 -0400 @@ -367,20 +367,15 @@ } private JMenuBar createMenuBar() { - return MenuProvider.installMenu(this); - } - - /** Creates menu bar. */ - private static JMenuBar xcreateMenuBar() { JMenuBar menu = getCustomMenuBar(); if (menu == null) { - menu = new MenuBar (null); + menu = MenuProvider.installMenu(this); + } else { + Logger.getLogger(MainWindow.class.getName()).log (Level.WARNING, + "Use of system property to create main menu deprecated. " + + "Instead, provide your own MenuProvider in the default " + + "Lookup."); } - menu.setBorderPainted(false); - if (menu instanceof MenuBar) { - ((MenuBar)menu).waitFinished(); - } - if(Constants.SWITCH_STATUSLINE_IN_MENUBAR) { if (Constants.CUSTOM_STATUS_LINE_PATH == null) { JLabel status = new StatusLine(); @@ -391,7 +386,7 @@ JPanel statusLinePanel = new JPanel(new BorderLayout()); statusLinePanel.add(sep, BorderLayout.WEST); statusLinePanel.add(status, BorderLayout.CENTER); - + decoratePanel (statusLinePanel, true); statusLinePanel.setName("statusLine"); //NOI18N menu.add(statusLinePanel); @@ -402,7 +397,6 @@ } } } - return menu; } diff -r 7a1c5c06f75b -r fdb5086bb983 openide.loaders/src/org/openide/awt/MenuBar.java --- a/openide.loaders/src/org/openide/awt/MenuBar.java Sat Mar 27 18:29:35 2010 -0400 +++ b/openide.loaders/src/org/openide/awt/MenuBar.java Sun Mar 28 00:53:50 2010 -0400 @@ -109,8 +109,10 @@ *

  • executable DataObjects * * + * @Deprecated No longer used * @author David Peroutka, Dafe Simonek, Petr Nejedly */ +@Deprecated public class MenuBar extends JMenuBar implements Externalizable { /** the folder which represents and loads content of the menubar */ diff -r 7a1c5c06f75b -r fdb5086bb983 openide.windows/arch.xml --- a/openide.windows/arch.xml Sat Mar 27 18:29:35 2010 -0400 +++ b/openide.windows/arch.xml Sun Mar 28 00:53:50 2010 -0400 @@ -695,8 +695,10 @@ -J-Dnetbeans.winsys.no_toolbars=true + Note: This way of replacing the menu bar is deprecated and may be removed. + Instead, implements MenuProvider to provide your own JMenuBar. If this property is set its value must point to a file on the system file system - that provides instance of type org.openide.awt.MenuBar. + that provides instance of type javax.swing.JMenuBar. This alternative menu bar will be used instead of the default one. -J-Dnetbeans.winsys.menu_bar.path=foo/bar.instance # HG changeset patch # User tboudreau@netbeans.org # Date 1269766313 14400 # Branch menus-rewrite-182698 # Node ID acac4fa25eaa37dd68507700967867a6608728de # Parent fdb5086bb983b03d5333335050051a1e0d778855 Move testing whether the InstanceCookie lied or not out of the initialization sequence diff -r fdb5086bb983 -r acac4fa25eaa api.menus/nbproject/project.properties --- a/api.menus/nbproject/project.properties Sun Mar 28 00:53:50 2010 -0400 +++ b/api.menus/nbproject/project.properties Sun Mar 28 04:51:53 2010 -0400 @@ -1,4 +1,5 @@ is.autoload=true javac.source=1.6 javac.compilerargs=-Xlint -Xlint:-serial +javadoc.arch=${basedir}/arch.xml nbm.module.author=Tim Boudreau diff -r fdb5086bb983 -r acac4fa25eaa core.menu/nbproject/project.properties --- a/core.menu/nbproject/project.properties Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/nbproject/project.properties Sun Mar 28 04:51:53 2010 -0400 @@ -1,2 +1,3 @@ javac.source=1.6 javac.compilerargs=-Xlint -Xlint:-serial +javadoc.arch=${basedir}/arch.xml diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/ActionModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ActionModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ActionModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -42,10 +42,13 @@ import java.io.IOException; import javax.swing.Action; import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; import javax.swing.JMenuItem; import org.openide.awt.AcceleratorBinding; import org.openide.awt.Actions; +import org.openide.awt.DynamicMenuContent; import org.openide.awt.Mnemonics; +import org.openide.cookies.InstanceCookie; import org.openide.loaders.DataObject; import org.openide.util.Exceptions; import org.openide.util.actions.BooleanStateAction; @@ -77,6 +80,19 @@ return old; } + @Override + protected void verifyType(Action o) throws ReplaceMeException { + if (o instanceof DynamicMenuContent) { + throw new ReplaceMeException(o); + } + if (o instanceof Presenter.Menu) { + throw new ReplaceMeException(o); + } + if (o instanceof JComponent) { + throw new ReplaceMeException(o); + } + } + private String getActionName(Action action) { String name = (String) action.getValue(Action.NAME); if (name == null && action instanceof SystemAction) { @@ -95,11 +111,20 @@ } @Override + Types type() { + return Types.ACTION; + } + + @Override protected JMenuItem findComponent() { try { Action action = get(); - if (action instanceof Presenter.Menu) { - throw new IllegalStateException("ActionModelEntry used for a menu presenter action"); + if (action instanceof Presenter.Menu || action instanceof DynamicMenuContent || action instanceof JComponent) { + ReplaceMeException e = new ReplaceMeException(action); + logLiarInstanceCookie(type(), e.getPreferredType(), + file().getLookup().lookup(InstanceCookie.class), file(), + action); + throw e; } JMenuItem item; if (action instanceof BooleanStateAction) { diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ComponentModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -57,6 +57,11 @@ } @Override + Types type() { + return Types.COMPONENT; + } + + @Override protected boolean initBackground(List> children, int currDepth) { return false; } diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/DynamicModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -56,6 +56,11 @@ } @Override + Types type() { + return Types.DYNAMIC_MENU_CONTENT; + } + + @Override protected JComponent[] findComponents() { try { DynamicMenuContent dyn = get(); diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/FileModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FileModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FileModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -72,13 +72,17 @@ return Component.class.isAssignableFrom(dobType); } - protected T get() throws IOException, ClassNotFoundException { + protected T get() throws IOException, ClassNotFoundException, ReplaceMeException { if (requiresEq() && !EventQueue.isDispatchThread()) { throw new IllegalStateException ("Not on event thread"); } return modelObjectType.cast(cookie().instanceCreate()); } + protected void verifyType (T o) throws ReplaceMeException { + //do nothing + } + private static final class EmptyInstanceCookie implements InstanceCookie, InstanceCookie.Of { private final String name; EmptyInstanceCookie (String name) { diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/FolderModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/FolderModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -50,17 +50,16 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.swing.Action; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JSeparator; -import org.openide.awt.DynamicMenuContent; import org.openide.cookies.InstanceCookie; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.util.Exceptions; import org.openide.util.Parameters; -import org.openide.util.actions.Presenter; /** * @@ -148,7 +147,6 @@ @Override protected boolean initBackground(List> children, int currDepth) { -// System.err.println("initBackground " + getName()); DataFolder fld = file(); boolean changed = true; Set> created = new HashSet>(); @@ -161,11 +159,24 @@ for (Types t : Types.values()) { if (t.accept(dob)) { ModelEntry e = t.createEntry(dob, this); + //XXX defer or batch afterward? + List> kids = new ArrayList>(); + boolean hasKids; + try { + hasKids = e.initBackground(kids, currDepth+1); + } catch (ReplaceMeException ex) { + Types type = ex.getPreferredType(); + logLiarInstanceCookie(t, type, dob.getCookie(InstanceCookie.class), dob, ex.culprit()); + if (type != null) { + e = ex.getPreferredType().createEntry(dob, this); + kids = new ArrayList>(); + hasKids = e.initBackground(kids, currDepth+1); + } else { + continue; + } + } created.add(e); children.add(e); - //XXX defer or batch afterward? - List> kids = new ArrayList>(); - boolean hasKids = e.initBackground(kids, currDepth+1); changed |= hasKids; if (hasKids) { if (e instanceof FolderModelEntry) { @@ -176,7 +187,9 @@ continue outer; } } -// System.err.println("No type accepted " + dob.getPrimaryFile().getPath() + " : " + ic(dob)); + Logger.getLogger(FolderModelEntry.class.getName()).log (Level.INFO, + "Unrecognized menu entry: {0} - not an Action, Presenter.Menu, folder, " + //NOI18N + "DynamicMenuContent or JComponent", dob.getPrimaryFile().getPath()); //NOI18N } catch (IOException ex) { Exceptions.printStackTrace(ex); } catch (ClassNotFoundException ex) { @@ -190,10 +203,46 @@ return changed; } -// private static String ic(DataObject dob) throws ClassNotFoundException, IOException { -// InstanceCookie ck = dob.getCookie(InstanceCookie.class); -// return ck == null ? " [no ic]" : ck.instanceClass().getName(); -// } + private JComponent[] fetchComponents (int index, ModelEntry e, List> loop, int[] offset) { + JComponent[] comps = null; + try { + //If an InstanceCookie claims to just provide an Action, + //but in fact provides, say, a Presenter.Menu, or + //DynamicMenuContent, an exception will be thrown and we + //have to replace the model object with the one we actually + //need + comps = e.getComponents(); + } catch (ReplaceMeException ex) { + Types t = ex.getPreferredType(); + if (t == e.type()) { + throw new IllegalStateException ("Keep getting same wrong type for " + e); + } + if (t == null) { + synchronized (childLock) { + children.remove (e); + } + loop.remove(index); + offset[0] = -1; + return null; + } else { + ModelEntry nue = t.createEntry(e.file(), this); + Logger.getLogger (FolderModelEntry.class.getName()).log(Level.FINE, + "Replace {0} with {1}", new Object[] { e, nue }); + loop.set(index, nue); + synchronized (childLock) { + int ix = children.indexOf(e); //May have changed, so look up the index + if (ix >= 0) { + children.set(ix, nue); + } + } + e = nue; + comps = e.getComponents(); + offset[0] = Integer.MAX_VALUE; + } + } + return comps; + } + @Override protected CompType refreshComponent(CompType old) { if (!EventQueue.isDispatchThread()) { @@ -205,11 +254,34 @@ List all = new LinkedList(); List> kids = children(); Boolean expectHasIcon = false; - for (ModelEntry e : kids) { + int max = kids.size(); + for (int i=0; i < max; i++) { + ModelEntry e = kids.get(i); ComponentList c = components(e); - JComponent[] comps; + JComponent[] comps = null; if (c == null) { - comps = e.getComponents(); + //A little tortured here, but... Some InstanceCookies, + //particularly ones registered with methodvalue, do not + //tell the truth about whether they are a Presenter.Menu + //or DynamicMenuContent. + //So, fetchComponents() will trigger actual instantiation, + //and if the model object type is wrong, it will replace it + //in the model...at which point we modify the list we're + //looping over and continue merrily onward + int[] offset = new int[1]; + comps = fetchComponents(i, e, kids, offset); + if (offset[0] < 0) { + //We got back an object that is not an Action or anything + //we can use, and the elemnent has been removed from the + //model + max += offset[0]; //subtraction + i += offset[0]; //subtraction + continue; + } else if (offset[0] == Integer.MAX_VALUE) { + //Signal that the model element has been replaced - + //fetch it again so we are using the replacement object + e = kids.get(i); + } if (comps != null) { all.addAll(Arrays.asList(comps)); setComponents(e, c = new ComponentList(comps)); @@ -224,7 +296,14 @@ c = newComps; } } else { - comps = e.findComponents(); + //If we get a ReplaceMeException here, then the return + //type has changed on the fly - we're just screwed. + try { + comps = e.findComponents(); + } catch (ReplaceMeException ex) { + Exceptions.printStackTrace(ex); + continue; + } boolean fixed = c.repair(comps); if (!fixed) { setComponents(e, c = new ComponentList(comps)); @@ -274,67 +353,8 @@ } } - private static enum Types { - - SEPARATOR(JSeparator.class), - DYNAMIC_MENU_CONTENT(DynamicMenuContent.class), - PRESENTER(Presenter.Menu.class), - MENU, - COMPONENT(Component.class), - ACTION(Action.class); - private final Class type; - - Types(Class type) { - this.type = type; - } - - Types() { - this(Object.class); - } - - boolean accept(DataObject dob) throws IOException, ClassNotFoundException { - if (this == MENU) { - DataFolder fld = dob instanceof DataFolder ? (DataFolder) dob - : dob.getLookup().lookup(DataFolder.class); - return fld != null; - } - InstanceCookie ck = dob.getLookup().lookup(InstanceCookie.class); - return ck instanceof InstanceCookie.Of ? ((InstanceCookie.Of) ck).instanceOf(type) - : ck == null ? false : type.isAssignableFrom(ck.instanceClass()); - } - - ModelEntry createEntry(DataObject ob, FolderModelEntry entry) { -// System.err.println("Create " + name() + " entry for " + ob.getPrimaryFile().getPath()); - switch (this) { - case SEPARATOR: - return new SeparatorModelEntry(ob); - case ACTION: - InstanceCookie ic = ob.getLookup().lookup(InstanceCookie.class); - try { - //This is a bit of a mess - some InstanceCookies lie - if (ic.instanceCreate() instanceof Presenter.Menu) { - return new PresenterEntry(ob); - } - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } catch (ClassNotFoundException ex) { - Exceptions.printStackTrace(ex); - } - return new ActionModelEntry(ob); - case DYNAMIC_MENU_CONTENT: - return new DynamicModelEntry(ob); - case PRESENTER: - return new PresenterEntry(ob); - case MENU: - DataFolder fld = ob instanceof DataFolder ? (DataFolder) ob - : ob.getLookup().lookup(DataFolder.class); - Parameters.notNull("folder was non-null but is now null", fld); - return new MenuEntry(fld, MenuManager.rp); - case COMPONENT: - return new ComponentModelEntry(ob); - default: - throw new AssertionError(this + ""); - } - } + @Override + final Types type() { + return Types.MENU; } } diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/ModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/ModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -41,10 +41,15 @@ import java.awt.EventQueue; import java.lang.reflect.Array; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenuItem; +import org.openide.cookies.InstanceCookie; import org.openide.loaders.DataObject; import org.openide.util.ImageUtilities; import org.openide.util.Parameters; @@ -67,7 +72,7 @@ Parameters.notNull("file", file); } - protected abstract boolean initBackground(List> children, int currDepth); + protected abstract boolean initBackground(List> children, int currDepth) throws ReplaceMeException; public String getName() { DataObject file = file(); @@ -78,6 +83,8 @@ return file; } + abstract Types type(); + final Class componentType() { return compType; } @@ -208,6 +215,28 @@ @Override public String toString() { - return super.toString() + "[" + getName() + "]"; + String path = file().isValid() ? file().getPrimaryFile().getPath() : getName(); + return super.toString() + "[" + path + "," + type() + "]"; //NOI18N + } + + + private static final Set loggedFiles = new HashSet(); + static void logLiarInstanceCookie (Types expect, Types got, InstanceCookie ic, DataObject ob, Object o) { + if (!loggedFiles.contains(ob.getPrimaryFile().getPath())) { + loggedFiles.add(ob.getPrimaryFile().getPath()); + Logger.getLogger(ModelEntry.class.getName()).log(Level.WARNING, //NOI18N + "{0} - InstanceCookie.instanceClass() does " + //NOI18N + "not return a type assignable to {5} " + //NOI18N + "but the object it creates is a {6}: {1} " + //NOI18N + "({2}). If using 'methodvalue' in a layer, add " + //NOI18N + "" + //NOI18N + "({3} ({4})", //NOI18N + new Object[] { + + ob.getPrimaryFile().getPath(), o, o.getClass().getName(), + ic, ic.getClass().getName(), expect.type().getName(), + got == null ? "null" : got.type().getName()}); + } } } diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/PresenterEntry.java --- a/core.menu/src/org/netbeans/core/menu/PresenterEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/PresenterEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -59,6 +59,11 @@ } @Override + Types type() { + return Types.PRESENTER; + } + + @Override protected JComponent[] findComponents() { try { Presenter.Menu p = super.get(); diff -r fdb5086bb983 -r acac4fa25eaa core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java --- a/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.menu/src/org/netbeans/core/menu/SeparatorModelEntry.java Sun Mar 28 04:51:53 2010 -0400 @@ -53,6 +53,11 @@ } @Override + public Types type() { + return Types.SEPARATOR; + } + + @Override protected JSeparator findComponent() { return new JSeparator(); } diff -r fdb5086bb983 -r acac4fa25eaa core.windows/src/org/netbeans/core/windows/actions/UndockWindowAction.java --- a/core.windows/src/org/netbeans/core/windows/actions/UndockWindowAction.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/actions/UndockWindowAction.java Sun Mar 28 04:51:53 2010 -0400 @@ -69,6 +69,7 @@ */ public UndockWindowAction () { this.tc = null; + putValue (NAME, NbBundle.getMessage(UndockWindowAction.class, "CTL_UndockWindowAction")); } /** @@ -77,6 +78,7 @@ */ public UndockWindowAction (TopComponent tc) { this.tc = tc; + putValue (NAME, NbBundle.getMessage(UndockWindowAction.class, "CTL_UndockWindowAction")); } public void actionPerformed (ActionEvent e) { diff -r fdb5086bb983 -r acac4fa25eaa core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java --- a/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sun Mar 28 00:53:50 2010 -0400 +++ b/core.windows/src/org/netbeans/core/windows/view/ui/MainWindow.java Sun Mar 28 04:51:53 2010 -0400 @@ -165,7 +165,9 @@ getAccessibleContext().setAccessibleDescription( NbBundle.getBundle(MainWindow.class).getString("ACSD_MainWindow")); - setJMenuBar(mainMenuBar); + if (mainMenuBar != null) { //can legally have an app with no menu + setJMenuBar(mainMenuBar); + } if (!Constants.NO_TOOLBARS) { JComponent tb = getToolbarComponent(); diff -r fdb5086bb983 -r acac4fa25eaa favorites/src/org/netbeans/modules/favorites/resources/layer.xml --- a/favorites/src/org/netbeans/modules/favorites/resources/layer.xml Sun Mar 28 00:53:50 2010 -0400 +++ b/favorites/src/org/netbeans/modules/favorites/resources/layer.xml Sun Mar 28 04:51:53 2010 -0400 @@ -63,6 +63,7 @@ + diff -r fdb5086bb983 -r acac4fa25eaa profiler/src/org/netbeans/modules/profiler/mf-layer.xml --- a/profiler/src/org/netbeans/modules/profiler/mf-layer.xml Sun Mar 28 00:53:50 2010 -0400 +++ b/profiler/src/org/netbeans/modules/profiler/mf-layer.xml Sun Mar 28 04:51:53 2010 -0400 @@ -394,6 +394,7 @@ + diff -r fdb5086bb983 -r acac4fa25eaa projectui/src/org/netbeans/modules/project/ui/resources/layer.xml --- a/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml Sun Mar 28 00:53:50 2010 -0400 +++ b/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml Sun Mar 28 04:51:53 2010 -0400 @@ -99,10 +99,12 @@ + + @@ -124,18 +126,22 @@ + + + + @@ -169,6 +175,7 @@ + @@ -176,6 +183,7 @@ + diff -r fdb5086bb983 -r acac4fa25eaa refactoring.api/src/org/netbeans/modules/refactoring/api/resources/layer.xml --- a/refactoring.api/src/org/netbeans/modules/refactoring/api/resources/layer.xml Sun Mar 28 00:53:50 2010 -0400 +++ b/refactoring.api/src/org/netbeans/modules/refactoring/api/resources/layer.xml Sun Mar 28 04:51:53 2010 -0400 @@ -114,15 +114,19 @@ + + + + diff -r fdb5086bb983 -r acac4fa25eaa spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml --- a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml Sun Mar 28 00:53:50 2010 -0400 +++ b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml Sun Mar 28 04:51:53 2010 -0400 @@ -148,9 +148,11 @@ + +