Mercurial > hg > release > thermostat-1.0
changeset 395:053bc97253ad
Nested dynamic menus
Reviewed-by: neugens, vanaltj
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-June/001845.html
line wrap: on
line diff
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/MainView.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/MainView.java Tue Jun 19 15:37:59 2012 -0400 @@ -78,9 +78,17 @@ void setSubView(Component view); - void addMenu(String parentMenuName, MenuAction action); + /** + * Adds a menu item to the window. Assumes the menu path is valid (has a + * non-zero length) and doesn't collide with existing menus. + */ + void addMenu(MenuAction action); - void removeMenu(String parentMenuName, MenuAction action); + /** + * Removes a menu item to the window. Assumes the menu path is valid (has a + * non-zero length) and doesn't collide with existing menus. + */ + void removeMenu(MenuAction action); void showVMContextActions(List<VMContextAction> actions, MouseEvent e); }
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/MainWindowControllerImpl.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/MainWindowControllerImpl.java Tue Jun 19 15:37:59 2012 -0400 @@ -97,22 +97,35 @@ private ApplicationInfo appInfo; private UiFacadeFactory facadeFactory; + + // FIXME: sort out the code duplication in the registry listeners + private MenuRegistry menuRegistry; - private MenuRegistry.MenuListener menuListener = new MenuRegistry.MenuListener() { - + private ActionListener<ThermostatExtensionRegistry.Action> menuListener = + new ActionListener<ThermostatExtensionRegistry.Action>() + { @Override - public void removed(String parentMenuName, MenuAction action) { - view.removeMenu(parentMenuName, action); - } + public void actionPerformed( + ActionEvent<ThermostatExtensionRegistry.Action> actionEvent) { + MenuAction action = (MenuAction) actionEvent.getPayload(); + + switch (actionEvent.getActionId()) { + case SERVICE_ADDED: + view.addMenu(action); + break; - @Override - public void added(String parentMenuName, MenuAction action) { - view.addMenu(parentMenuName, action); + case SERVICE_REMOVED: + view.removeMenu(action); + break; + + default: + logger.log(Level.WARNING, "received unknown event from MenuRegistry: " + + actionEvent.getActionId()); + break; + } } }; - // FIXME: sort out the code duplication in the registry listeners - private TreeViewFilter treeFilter; private VMTreeFilterRegistry filterRegistry; private ActionListener<ThermostatExtensionRegistry.Action> filterListener = @@ -220,7 +233,7 @@ updateView(); - menuRegistry.addMenuListener(menuListener); + menuRegistry.addActionListener(menuListener); menuRegistry.start(); filterRegistry.addActionListener(filterListener); @@ -252,23 +265,23 @@ } /** - * This method is for testing purpouse only + * This method is for testing purposes only */ Filter getTreeFilter() { return treeFilter; } /** - * This method is for testing purpouse only + * This method is for testing purposes only */ List<ReferenceDecorator> getVmTreeDecorators() { return vmTreeDecorators; } /** - * This method is for testing purpouse only + * This method is for testing purposes only */ - MenuRegistry.MenuListener getMenuListener() { + ActionListener<ThermostatExtensionRegistry.Action> getMenuListener() { return menuListener; } @@ -373,7 +386,7 @@ } private void shutdownApplication() { - menuRegistry.removeMenuListener(menuListener); + menuRegistry.removeActionListener(menuListener); menuListener = null; menuRegistry.stop();
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/MenuRegistry.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/MenuRegistry.java Tue Jun 19 15:37:59 2012 -0400 @@ -35,108 +35,18 @@ */ package com.redhat.thermostat.client.internal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CopyOnWriteArrayList; - import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; import com.redhat.thermostat.client.osgi.service.MenuAction; -public class MenuRegistry { - - public static interface MenuListener { - - public void added(String parentMenuName, MenuAction action); - - public void removed(String parentMenuName, MenuAction action); - } +public class MenuRegistry extends ThermostatExtensionRegistry<MenuAction> { - public static final String PARENT_MENU = "parentMenu"; - - private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" + MenuAction.class.getName() + ")(" + PARENT_MENU + "=*))"; - - private ServiceTracker menuTracker; - - private Map<String,List<MenuAction>> menus = new HashMap<>(); - private List<MenuListener> listeners = new CopyOnWriteArrayList<>(); + private static final String FILTER = "(" + Constants.OBJECTCLASS + "=" + MenuAction.class.getName() + ")"; public MenuRegistry(BundleContext context) throws InvalidSyntaxException { - menuTracker = new ServiceTracker(context, FrameworkUtil.createFilter(FILTER), null) { - @Override - public Object addingService(ServiceReference reference) { - MenuAction action = (MenuAction) super.addingService(reference); - String parentMenuName = (String) reference.getProperty(PARENT_MENU); - menuAdded(parentMenuName, action); - return action; - } - - @Override - public void removedService(ServiceReference reference, Object service) { - if (!(service instanceof MenuAction)) { - throw new AssertionError("removing a non-MenuAction service"); - } - String parentMenuName = (String) reference.getProperty(PARENT_MENU); - menuRemoved(parentMenuName, (MenuAction)service); - super.removedService(reference, service); - } - }; - } - - public void start() { - menuTracker.open(); - } - - public void stop() { - menuTracker.close(); - } - - public void addMenuListener(MenuListener listener) { - listeners.add(listener); - - for (Entry<String,List<MenuAction>> entry: menus.entrySet()) { - for (MenuAction action: entry.getValue()) { - listener.added(entry.getKey(), action); - } - } - } - - public void removeMenuListener(MenuListener listener) { - listeners.remove(listener); - } - - private void menuAdded(String parentMenuName, MenuAction action) { - if (!menus.containsKey(parentMenuName)) { - menus.put(parentMenuName, new ArrayList<MenuAction>()); - } - List<MenuAction> list = menus.get(parentMenuName); - list.add(action); - for (MenuListener listener: listeners) { - listener.added(parentMenuName, action); - } - } - - private void menuRemoved(String parentMenuName, MenuAction action) { - if (!menus.containsKey(parentMenuName)) { - throw new IllegalArgumentException("unknown parent menu name"); - } - List<MenuAction> list = menus.get(parentMenuName); - if (!list.contains(action)) { - throw new IllegalArgumentException("unknown menu action"); - } - - list.remove(action); - for (MenuListener listener: listeners) { - listener.removed(parentMenuName, action); - } + super(context, FILTER, MenuAction.class); } }
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/ThermostatExtensionRegistry.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/ThermostatExtensionRegistry.java Tue Jun 19 15:37:59 2012 -0400 @@ -45,6 +45,9 @@ import com.redhat.thermostat.common.ActionListener; import com.redhat.thermostat.common.ActionNotifier; +/** + * A helper class to make it easier to implement registeries for osgi-services. + */ public class ThermostatExtensionRegistry<E> { public enum Action { @@ -93,7 +96,7 @@ actionNotifier.addActionListener(l); } - public void removeViewActionListener(ActionListener<Action> l) { + public void removeActionListener(ActionListener<Action> l) { actionNotifier.removeActionListener(l); } }
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/VMTreeDecoratorRegistry.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/VMTreeDecoratorRegistry.java Tue Jun 19 15:37:59 2012 -0400 @@ -44,7 +44,7 @@ class VMTreeDecoratorRegistry extends ThermostatExtensionRegistry<ReferenceDecorator> { - private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" + ReferenceDecorator.class.getName() + "))"; + private static final String FILTER = "(" + Constants.OBJECTCLASS + "=" + ReferenceDecorator.class.getName() + ")"; public VMTreeDecoratorRegistry(BundleContext context) throws InvalidSyntaxException { super(context, FILTER, ReferenceDecorator.class);
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/VMTreeFilterRegistry.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/VMTreeFilterRegistry.java Tue Jun 19 15:37:59 2012 -0400 @@ -44,7 +44,7 @@ class VMTreeFilterRegistry extends ThermostatExtensionRegistry<Filter> { - private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" + Filter.class.getName() + "))"; + private static final String FILTER = "(" + Constants.OBJECTCLASS + "=" + Filter.class.getName() + ")"; public VMTreeFilterRegistry(BundleContext context) throws InvalidSyntaxException { super(context, FILTER, Filter.class);
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java Tue Jun 19 15:37:59 2012 -0400 @@ -44,15 +44,17 @@ */ public interface MenuAction extends ContextAction { - public static enum TYPE { + public static enum Type { CHECK, RADIO, STANDARD }; - - public static final String PARENT_MENU = "parentMenu"; - + /** Invoked when the user selects this menu item */ void execute(); - TYPE getType(); + + Type getType(); + + /** The path to the menu action. The last element must equal getName() */ + String[] getPath(); }
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java Tue Jun 19 15:37:59 2012 -0400 @@ -76,13 +76,11 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.KeyStroke; -import javax.swing.MenuElement; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.ToolTipManager; @@ -104,10 +102,10 @@ import com.redhat.thermostat.client.internal.HostsVMsLoader; import com.redhat.thermostat.client.internal.MainView; import com.redhat.thermostat.client.locale.LocaleResources; +import com.redhat.thermostat.client.osgi.service.Filter; import com.redhat.thermostat.client.osgi.service.MenuAction; import com.redhat.thermostat.client.osgi.service.ReferenceDecorator; import com.redhat.thermostat.client.osgi.service.VMContextAction; -import com.redhat.thermostat.client.osgi.service.Filter; import com.redhat.thermostat.common.ActionListener; import com.redhat.thermostat.common.ActionNotifier; import com.redhat.thermostat.common.dao.HostRef; @@ -263,7 +261,8 @@ private static final long serialVersionUID = 5608972421496808177L; - private final JMenuBar mainMenuBar; + private final JMenuBar mainMenuBar = new JMenuBar(); + private final MenuHelper mainMenuHelper = new MenuHelper(mainMenuBar); private JPanel contentArea = null; private JTextField searchField = null; @@ -294,8 +293,6 @@ ToolTipManager.sharedInstance().registerComponent(agentVmTree); contentArea = new JPanel(new BorderLayout()); - mainMenuBar = new JMenuBar(); - setupMenus(); setupPanels(); @@ -732,108 +729,17 @@ } }); } - + @Override - public void addMenu(final String parentMenuName, final MenuAction action) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - JMenu parent = null; - int mainMenuCount = mainMenuBar.getMenuCount(); - for (int i = 0; i < mainMenuCount; i++) { - if (mainMenuBar.getMenu(i).getText().equals(parentMenuName)) { - parent = mainMenuBar.getMenu(i); - break; - } - } - if (parent == null) { - parent = new JMenu(parentMenuName); - mainMenuBar.add(parent); - } - - JMenuItem menu = null; - switch (action.getType()) { - case RADIO: - menu = new JRadioButtonMenuItem(); - break; - case CHECK: - menu = new JCheckBoxMenuItem(); - break; - - case STANDARD: - default: - menu = new JMenuItem(); - break; - } - - menu.setText(action.getName()); - menu.addActionListener(new java.awt.event.ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - action.execute(); - } - }); - parent.add(menu); - - mainMenuBar.revalidate(); - } - }); + public void addMenu(MenuAction action) { + mainMenuHelper.addMenuAction(action); } @Override - public void removeMenu(final String parentMenuName, final MenuAction action) { - final String actionName = action.getName(); - try { - new EdtHelper().callAndWait(new Runnable() { - @Override - public void run() { - MenuElement parent = null; - int mainMenuCount = mainMenuBar.getMenuCount(); - for (int i = 0; i < mainMenuCount; i++) { - if (mainMenuBar.getMenu(i).getText().equals(parentMenuName)) { - parent = mainMenuBar.getMenu(i); - break; - } - } - if (parent == null) { - throw new IllegalArgumentException("parent menu not found"); - } - - boolean removed = false; - MenuElement[] menus = parent.getSubElements(); - if (menus.length == 1 && (menus[0] instanceof JPopupMenu)) { - parent = menus[0]; - menus = parent.getSubElements(); - } + public void removeMenu(MenuAction action) { + mainMenuHelper.removeMenuAction(action); + } - for (MenuElement menu: menus) { - if (menu instanceof JMenuItem && ((JMenuItem)menu).getText().equals(actionName)) { - if (parent instanceof JPopupMenu) { - ((JPopupMenu)parent).remove((JMenuItem)menu); - removed = true; - } - } - } - - if (!removed) { - throw new IllegalArgumentException("child menu not found"); - } - - mainMenuBar.revalidate(); - } - }); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new RuntimeException(ie); - } catch (InvocationTargetException roe) { - Throwable cause = roe.getCause(); - if (cause instanceof IllegalArgumentException) { - throw (IllegalArgumentException) cause; - } - throw new RuntimeException(cause); - } - } - /** * Returns null to indicate no Ref is selected */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/MenuHelper.java Tue Jun 19 15:37:59 2012 -0400 @@ -0,0 +1,286 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.ui; + +import java.awt.event.ActionEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.logging.Logger; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.MenuElement; + +import com.redhat.thermostat.client.osgi.service.MenuAction; +import com.redhat.thermostat.common.utils.LoggingUtils; +import com.redhat.thermostat.common.utils.StringUtils; + +public class MenuHelper { + + private static final Logger logger = LoggingUtils.getLogger(MenuHelper.class); + + private final JMenuBar menuBar; + + public MenuHelper(JMenuBar menuBar) { + this.menuBar = menuBar; + } + + /** + * Add a menu item as specified though the {@link MenuAction} argument. + */ + public void addMenuAction(final MenuAction action) { + try { + new EdtHelper().callAndWait(new Runnable() { + @Override + public void run() { + String[] path = action.getPath(); + Menu parent = findMenuParent(menuBar, path, true); + JMenuItem menu = null; + switch (action.getType()) { + case RADIO: + menu = new JRadioButtonMenuItem(); + break; + case CHECK: + menu = new JCheckBoxMenuItem(); + break; + + case STANDARD: + default: + menu = new JMenuItem(); + break; + } + + menu.setText(action.getName()); + menu.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + action.execute(); + } + }); + parent.add(new Menu(menu)); + + menuBar.revalidate(); + } + }); + } catch (InvocationTargetException ite) { + Throwable cause = ite.getCause(); + if (cause instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cause; + } + throw new RuntimeException(cause); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + + } + + /** + * Remove a existing menu item as specified though the {@link MenuAction} + * argument. + * + * @throws IllegalArgumentException if the path specified in + * {@link MenuAction#getPath()} can not be found + */ + public void removeMenuAction(final MenuAction action) { + try { + new EdtHelper().callAndWait(new Runnable() { + @Override + public void run() { + String[] path = action.getPath(); + Menu parent = findMenuParent(menuBar, path, false); + parent.remove(path[path.length - 1]); + menuBar.revalidate(); + } + }); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } catch (InvocationTargetException roe) { + Throwable cause = roe.getCause(); + if (cause instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cause; + } + throw new RuntimeException(cause); + } + + } + + private static Menu findMenuParent(JMenuBar menuBar, String[] path, boolean createIfNotFound) { + Menu parent = null; + int mainMenuCount = menuBar.getMenuCount(); + for (int i = 0; i < mainMenuCount; i++) { + JMenu menu = menuBar.getMenu(i); + if (menu.getText().equals(path[0])) { + parent = new Menu(menuBar.getMenu(i)); + break; + } + } + if (parent == null) { + if (createIfNotFound) { + JMenu delegate = new JMenu(path[0]); + parent = new Menu(delegate); + menuBar.add(delegate); + } else { + throw new IllegalArgumentException("top-level " + path[0] + " not found (using path" + Arrays.toString(path) + ")"); + } + } + + for (int i = 1; i < path.length - 1; i++) { + Menu[] children = parent.children(); + boolean found = false; + for (int j = 0; j < children.length; j++) { + Menu menu = children[j]; + if (menu.getText().equals(path[i])) { + parent = menu; + found = true; + } + } + if (!found) { + if (createIfNotFound) { + Menu newMenu = new Menu(new JMenu(path[i])); + parent.add(newMenu); + parent = newMenu; + } else { + throw new IllegalArgumentException("path not found"); + } + } + } + + return parent; + } + + private static String getText(MenuElement element) { + if (element instanceof JMenuItem) { + return ((JMenuItem) element).getText(); + } + return ""; + } + + @SuppressWarnings("unused") // this method is for debugging only + private static void printMenu(MenuElement parent, int nestingLevel) { + System.out.println(StringUtils.repeat(" ", nestingLevel * 2) + getText(parent) + " [" + parent.getClass() + "]"); + for (MenuElement element : parent.getSubElements()) { + printMenu(element, nestingLevel + 1); + } + } + + /** + * The swing menu hierarchy makes uniform operations very difficult. This + * is a hack around that. + */ + private static final class Menu { + private Object swingDelegate; + + public Menu() { /* no op */} + + public Menu(JMenuItem actual) { + this.swingDelegate = actual; + } + + public String getText() { + if (swingDelegate instanceof JMenuItem) { + return ((JMenuItem) swingDelegate).getText(); + } + return null; + } + + public Menu[] children() { + if (swingDelegate instanceof MenuElement) { + MenuElement[] actualChildren = ((MenuElement) swingDelegate).getSubElements(); + if (actualChildren.length == 1 && actualChildren[0] instanceof JPopupMenu) { + actualChildren = actualChildren[0].getSubElements(); + } + + Menu[] children = new Menu[actualChildren.length]; + for (int i = 0; i < children.length; i++) { + children[i] = new Menu((JMenuItem) actualChildren[i]); + } + return children; + } + return new Menu[0]; + } + + public void add(Menu menu) { + if (swingDelegate instanceof JPopupMenu) { + ((JPopupMenu) swingDelegate).add((JMenuItem) menu.swingDelegate); + } else if (swingDelegate instanceof JMenu) { + ((JMenu) swingDelegate).add((JMenuItem) menu.swingDelegate); + } else { + logger.warning("Unable to add menu. Menu is of unrecognized type: " + menu.swingDelegate); + } + } + + public void remove(String string) { + JPopupMenu removeParent = null; + + if (swingDelegate instanceof JMenu) { + JMenu parent = (JMenu) swingDelegate; + MenuElement[] actualChildren = parent.getSubElements(); + if (actualChildren.length == 1 && actualChildren[0] instanceof JPopupMenu) { + removeParent = (JPopupMenu) actualChildren[0]; + } + } + + if (removeParent == null) { + if (swingDelegate instanceof JPopupMenu) { + removeParent = (JPopupMenu) swingDelegate; + } else { + logger.warning("BUG: problem while removing menu. delegate is not a JPopupMenu, cant remove"); + return; + } + } + + JPopupMenu parent = removeParent; + for (MenuElement element : parent.getSubElements()) { + if (((JMenuItem) element).getText().equals(string)) { + parent.remove((JMenuItem) element); + } + } + } + + @Override + public String toString() { + return "Menu [" + swingDelegate.toString() + "]"; + } + } + +}
--- a/client/core/src/test/java/com/redhat/thermostat/client/internal/MainWindowControllerImplTest.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/test/java/com/redhat/thermostat/client/internal/MainWindowControllerImplTest.java Tue Jun 19 15:37:59 2012 -0400 @@ -76,6 +76,7 @@ import com.redhat.thermostat.client.internal.UiFacadeFactory; import com.redhat.thermostat.client.internal.VMTreeDecoratorRegistry; import com.redhat.thermostat.client.internal.VMTreeFilterRegistry; +import com.redhat.thermostat.client.internal.ThermostatExtensionRegistry.Action; import com.redhat.thermostat.client.osgi.service.Filter; import com.redhat.thermostat.client.osgi.service.MenuAction; import com.redhat.thermostat.client.osgi.service.ReferenceDecorator; @@ -120,7 +121,7 @@ private VMTreeFilterRegistry filters; private VMTreeDecoratorRegistry decorators; private VMInformationRegistry vmInfoRegistry; - private MenuRegistry menues; + private MenuRegistry menus; private ActionListener<ThermostatExtensionRegistry.Action> filtersListener; private ActionListener<ThermostatExtensionRegistry.Action> decoratorsListener; @@ -158,9 +159,9 @@ filters = mock(VMTreeFilterRegistry.class); decorators = mock(VMTreeDecoratorRegistry.class); vmInfoRegistry = mock(VMInformationRegistry.class); - menues = mock(MenuRegistry.class); + menus = mock(MenuRegistry.class); - when(registryFactory.createMenuRegistry()).thenReturn(menues); + when(registryFactory.createMenuRegistry()).thenReturn(menus); when(registryFactory.createVMTreeDecoratorRegistry()).thenReturn(decorators); when(registryFactory.createVMTreeFilterRegistry()).thenReturn(filters); when(registryFactory.createVMInformationRegistry()).thenReturn(vmInfoRegistry); @@ -519,16 +520,21 @@ @Test public void verifyMenuItems() { - MenuRegistry.MenuListener menuListener = controller.getMenuListener(); + ActionListener<ThermostatExtensionRegistry.Action> menuListener = controller.getMenuListener(); MenuAction action = mock(MenuAction.class); when(action.getName()).thenReturn("Test1"); - menuListener.added("File", action); - verify(view).addMenu("File", action); + ActionEvent<Action> addEvent = new ActionEvent<ThermostatExtensionRegistry.Action>( + menus, ThermostatExtensionRegistry.Action.SERVICE_ADDED); + addEvent.setPayload(action); + menuListener.actionPerformed(addEvent); + verify(view).addMenu(action); - menuListener.removed("File", action); - verify(view).removeMenu("File", action); + ActionEvent<Action> removeEvent = new ActionEvent<ThermostatExtensionRegistry.Action>(menus, ThermostatExtensionRegistry.Action.SERVICE_REMOVED); + removeEvent.setPayload(action); + menuListener.actionPerformed(removeEvent); + verify(view).removeMenu(action); } @Test
--- a/client/core/src/test/java/com/redhat/thermostat/client/internal/MenuRegistryTest.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/test/java/com/redhat/thermostat/client/internal/MenuRegistryTest.java Tue Jun 19 15:37:59 2012 -0400 @@ -36,10 +36,10 @@ package com.redhat.thermostat.client.internal; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isA; +import static org.junit.Assert.*; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -52,8 +52,9 @@ import org.osgi.framework.ServiceReference; import com.redhat.thermostat.client.internal.MenuRegistry; -import com.redhat.thermostat.client.internal.MenuRegistry.MenuListener; import com.redhat.thermostat.client.osgi.service.MenuAction; +import com.redhat.thermostat.common.ActionEvent; +import com.redhat.thermostat.common.ActionListener; public class MenuRegistryTest { @@ -62,7 +63,7 @@ ArgumentCaptor<ServiceListener> serviceListenerCaptor = ArgumentCaptor.forClass(ServiceListener.class); ArgumentCaptor<String> filterCaptor = ArgumentCaptor.forClass(String.class); - MenuListener menuListener = mock(MenuListener.class); + ActionListener<ThermostatExtensionRegistry.Action> menuListener = mock(ActionListener.class); MenuAction menuAction = mock(MenuAction.class); BundleContext context = mock(BundleContext.class); @@ -70,22 +71,26 @@ ServiceReference ref = mock(ServiceReference.class); when(ref.getProperty("objectClass")).thenReturn(MenuAction.class.getName()); - when(ref.getProperty(MenuRegistry.PARENT_MENU)).thenReturn("Test"); when(context.getService(ref)).thenReturn(menuAction); MenuRegistry registry = new MenuRegistry(context); - registry.addMenuListener(menuListener); + registry.addActionListener(menuListener); registry.start(); ServiceListener serviceListener = serviceListenerCaptor.getValue(); serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, ref)); - verify(menuListener).added(eq("Test"), isA(MenuAction.class)); - + ArgumentCaptor<ActionEvent> eventCaptor = ArgumentCaptor.forClass(ActionEvent.class); serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, ref)); - verify(menuListener).removed(eq("Test"), isA(MenuAction.class)); + verify(menuListener, times(2)).actionPerformed(eventCaptor.capture()); + + ActionEvent firstEvent = eventCaptor.getAllValues().get(0); + ActionEvent secondEvent = eventCaptor.getAllValues().get(1); + + assertEquals(ThermostatExtensionRegistry.Action.SERVICE_ADDED, firstEvent.getActionId()); + assertEquals(ThermostatExtensionRegistry.Action.SERVICE_REMOVED, secondEvent.getActionId()); } }
--- a/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java Tue Jun 19 15:37:59 2012 -0400 @@ -252,27 +252,29 @@ @Category(GUITest.class) @Test public void addRemoveMenu() { - final String MENU_NAME = "Test"; + final String PARENT_NAME = "File"; + final String MENU_NAME = "Test2"; MenuAction action = mock(MenuAction.class); when(action.getName()).thenReturn(MENU_NAME); - when(action.getType()).thenReturn(MenuAction.TYPE.STANDARD); + when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME}); + when(action.getType()).thenReturn(MenuAction.Type.STANDARD); JMenuItemFixture menuItem; frameFixture.show(); - window.addMenu("File", action); + window.addMenu(action); - menuItem = frameFixture.menuItemWithPath("File", MENU_NAME); + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); assertNotNull(menuItem); menuItem.click(); verify(action).execute(); - window.removeMenu("File", action); + window.removeMenu(action); try { - menuItem = frameFixture.menuItemWithPath("File", MENU_NAME); + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); // should not reach here assertTrue(false); } catch (ComponentLookupException cle) { @@ -283,18 +285,22 @@ @Category(GUITest.class) @Test public void addRadioMenu() { + final String PARENT_NAME = "File"; final String MENU_NAME = "Test"; MenuAction action = mock(MenuAction.class); when(action.getName()).thenReturn(MENU_NAME); - when(action.getType()).thenReturn(MenuAction.TYPE.RADIO); + when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME}); + + + when(action.getType()).thenReturn(MenuAction.Type.RADIO); JMenuItemFixture menuItem; frameFixture.show(); - window.addMenu("File", action); + window.addMenu(action); - menuItem = frameFixture.menuItemWithPath("File", MENU_NAME); + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); assertNotNull(menuItem); assertTrue(menuItem.target instanceof JRadioButtonMenuItem); @@ -303,18 +309,21 @@ @Category(GUITest.class) @Test public void addCheckBoxMenu() { + final String PARENT_NAME = "File"; final String MENU_NAME = "Test"; MenuAction action = mock(MenuAction.class); when(action.getName()).thenReturn(MENU_NAME); - when(action.getType()).thenReturn(MenuAction.TYPE.CHECK); + when(action.getType()).thenReturn(MenuAction.Type.CHECK); + when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME}); + JMenuItemFixture menuItem; frameFixture.show(); - window.addMenu("File", action); + window.addMenu(action); - menuItem = frameFixture.menuItemWithPath("File", MENU_NAME); + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); assertNotNull(menuItem); assertTrue(menuItem.target instanceof JCheckBoxMenuItem);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/core/src/test/java/com/redhat/thermostat/client/ui/MenuHelperTest.java Tue Jun 19 15:37:59 2012 -0400 @@ -0,0 +1,241 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.ui; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JRadioButtonMenuItem; + +import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner; + +import org.fest.swing.annotation.GUITest; +import org.fest.swing.edt.FailOnThreadViolationRepaintManager; +import org.fest.swing.edt.GuiActionRunner; +import org.fest.swing.edt.GuiTask; +import org.fest.swing.exception.ComponentLookupException; +import org.fest.swing.fixture.FrameFixture; +import org.fest.swing.fixture.JMenuItemFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import com.redhat.thermostat.client.osgi.service.MenuAction; + +@RunWith(CacioFESTRunner.class) +public class MenuHelperTest { + + private FrameFixture frameFixture; + private JFrame window; + private MenuHelper menu; + + @BeforeClass + public static void setUpOnce() { + FailOnThreadViolationRepaintManager.install(); + } + + @Before + public void setUp() { + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + window = new JFrame(); + JMenuBar menuBar = new JMenuBar(); + window.setJMenuBar(menuBar); + JMenu fileMenu = new JMenu("File"); + fileMenu.setName("File"); + menuBar.add(fileMenu); + menu = new MenuHelper(menuBar); + window.pack(); + } + }); + + frameFixture = new FrameFixture(window); + } + + @After + public void tearDown() { + frameFixture.cleanUp(); + frameFixture = null; + window = null; + } + + @Category(GUITest.class) + @Test + public void addRemoveWithNewTopLevelMenu() { + final String PARENT_NAME = "Test1"; + final String MENU_NAME = "Test2"; + MenuAction action = mock(MenuAction.class); + when(action.getName()).thenReturn(MENU_NAME); + when(action.getPath()).thenReturn(new String[] { PARENT_NAME, MENU_NAME }); + when(action.getType()).thenReturn(MenuAction.Type.STANDARD); + + JMenuItemFixture menuItem; + + frameFixture.show(); + + menu.addMenuAction(action); + + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + assertNotNull(menuItem); + + menu.removeMenuAction(action); + + try { + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + // should not reach here + assertTrue(false); + } catch (ComponentLookupException cle) { + // expected + } + } + + @Category(GUITest.class) + @Test + public void addRemoveToExistingMenu() { + final String PARENT_NAME = "File"; + final String MENU_NAME = "Test2"; + MenuAction action = mock(MenuAction.class); + when(action.getName()).thenReturn(MENU_NAME); + when(action.getPath()).thenReturn(new String[] { PARENT_NAME, MENU_NAME }); + when(action.getType()).thenReturn(MenuAction.Type.STANDARD); + + JMenuItemFixture menuItem; + + frameFixture.show(); + + assertNotNull(frameFixture.menuItem("File")); + + menu.addMenuAction(action); + + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + assertNotNull(menuItem); + + menu.removeMenuAction(action); + + try { + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + // should not reach here + assertTrue(false); + } catch (ComponentLookupException cle) { + // expected + } + } + + @Category(GUITest.class) + @Test + public void addRemoveHighlyNextedMenu() { + final String[] path = new String[] { "View", "Filter", "Virtual Machine", "Show Only Running" }; + MenuAction action = mock(MenuAction.class); + when(action.getName()).thenReturn(path[path.length - 1]); + when(action.getPath()).thenReturn(path); + when(action.getType()).thenReturn(MenuAction.Type.STANDARD); + + JMenuItemFixture menuItem; + + frameFixture.show(); + + menu.addMenuAction(action); + + menuItem = frameFixture.menuItemWithPath(path); + assertNotNull(menuItem); + + menu.removeMenuAction(action); + + try { + menuItem = frameFixture.menuItemWithPath(path); + // should not reach here + assertTrue(false); + } catch (ComponentLookupException cle) { + // expected + } + } + + @Category(GUITest.class) + @Test + public void addRadioMenu() { + final String PARENT_NAME = "File"; + final String MENU_NAME = "Test"; + MenuAction action = mock(MenuAction.class); + when(action.getName()).thenReturn(MENU_NAME); + when(action.getPath()).thenReturn(new String[] { PARENT_NAME, MENU_NAME }); + when(action.getType()).thenReturn(MenuAction.Type.RADIO); + + JMenuItemFixture menuItem; + + frameFixture.show(); + + menu.addMenuAction(action); + + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + assertNotNull(menuItem); + + assertTrue(menuItem.target instanceof JRadioButtonMenuItem); + } + + @Category(GUITest.class) + @Test + public void addCheckBoxMenu() { + final String PARENT_NAME = "File"; + final String MENU_NAME = "Test"; + MenuAction action = mock(MenuAction.class); + when(action.getName()).thenReturn(MENU_NAME); + when(action.getType()).thenReturn(MenuAction.Type.CHECK); + when(action.getPath()).thenReturn(new String[] { PARENT_NAME, MENU_NAME }); + + JMenuItemFixture menuItem; + + frameFixture.show(); + + menu.addMenuAction(action); + + menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME); + assertNotNull(menuItem); + + assertTrue(menuItem.target instanceof JCheckBoxMenuItem); + } + +}
--- a/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/LivingVMFilterMenuAction.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/LivingVMFilterMenuAction.java Tue Jun 19 15:37:59 2012 -0400 @@ -40,8 +40,6 @@ class LivingVMFilterMenuAction implements MenuAction { - public static final String PARENT_MENU = "Edit"; - private LivingVMFilter filter; public LivingVMFilterMenuAction(LivingVMFilter filter) { @@ -64,7 +62,12 @@ } @Override - public TYPE getType() { - return TYPE.CHECK; + public Type getType() { + return Type.CHECK; + } + + @Override + public String[] getPath() { + return new String[] { "Edit", getName() }; } }
--- a/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMFilterActivator.java Tue Jun 19 20:58:05 2012 +0200 +++ b/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMFilterActivator.java Tue Jun 19 15:37:59 2012 -0400 @@ -56,9 +56,6 @@ ServiceTracker tracker = new ServiceTracker(context, ApplicationService.class.getName(), null) { @Override public Object addingService(ServiceReference reference) { - Hashtable<String, String> props = new Hashtable<>(); - props.put(MenuAction.PARENT_MENU, LivingVMFilterMenuAction.PARENT_MENU); - ApplicationService service = (ApplicationService) context.getService(reference); LivingVMFilter filter = new LivingVMFilter(service.getDAOFactory()); @@ -71,7 +68,7 @@ context.registerService(ReferenceDecorator.class.getName(), decorator, null); context.registerService(Filter.class.getName(), filter, null); - context.registerService(MenuAction.class.getName(), menu, props); + context.registerService(MenuAction.class.getName(), menu, null); return super.addingService(reference); }