/******************************************************************************* * Copyright (c) 2008, 2015 Ketan Padegaonkar and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Ketan Padegaonkar - initial API and implementation * Patrick Tasse - Improve SWTBot menu API and implementation (Bug 479091) *******************************************************************************/ package org.eclipse.swtbot.swt.finder.finders; import static org.eclipse.swtbot.swt.finder.utils.SWTUtils.createEvent; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import org.apache.log4j.Logger; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swtbot.swt.finder.results.ArrayResult; import org.eclipse.swtbot.swt.finder.results.ListResult; import org.eclipse.swtbot.swt.finder.results.WidgetResult; import org.eclipse.swtbot.swt.finder.utils.SWTUtils; import org.hamcrest.Matcher; /** * Finds menus matching a particular matcher. * * @see UIThreadRunnable * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> * @version $Id$ */ public class MenuFinder { /** * The logging instance for this class. */ private static final Logger log = Logger.getLogger(MenuFinder.class); /** The display */ protected final Display display; /** * Creates a MenuFinder. */ public MenuFinder() { display = SWTUtils.display(); } /** * Finds the menu item matching the given matcher in the given shell. If * recursive is set, it will attempt to find the menu item recursively * depth-first in each of the sub-menus that are found. * * @param shell the shell to probe for menus. * @param matcher the matcher that can match menu items. * @param recursive if set to true, will find depth-first in sub-menus as well. * @param index the index of the menu item, in case there are multiple matching menu items. * @return the menu item in the specified shell that matches the matcher, or null. * @since 2.4 */ public MenuItem findMenuItem(final Shell shell, Matcher<MenuItem> matcher, boolean recursive, int index) { return findMenuItem(menuBar(shell), matcher, recursive, index); } /** * Finds all menu items matching the given matcher in all available shells. * It will attempt to find the menu items recursively in each of the * sub-menus that are found. * * @param matcher the matcher that can match menus and menu items. * @return all menu items in all shells that match the matcher. */ public List<MenuItem> findMenus(Matcher<MenuItem> matcher) { return findMenus(getShells(), matcher, true); } /** * Finds all menu items matching the given matcher in the set of shells * provided. If recursive is set, it will attempt to find the menu items * recursively in each of the sub-menus that are found. * * @param shells the shells to probe for menus. * @param matcher the matcher that can match menus and menu items. * @param recursive if set to true, will find depth-first in sub-menus as well. * @return all menu items in the specified shells that match the matcher. */ public List<MenuItem> findMenus(Shell[] shells, Matcher<MenuItem> matcher, boolean recursive) { LinkedHashSet<MenuItem> result = new LinkedHashSet<MenuItem>(); for (Shell shell : shells) { List<MenuItem> findMenus = findMenus(shell, matcher, recursive); result.addAll(findMenus); } return new ArrayList<MenuItem>(result); } /** * Finds all menu items matching the given matcher in the given shell. If * recursive is set, it will attempt to find the menu items recursively in * each of the sub-menus that are found. * * @param shell the shell to probe for menus. * @param matcher the matcher that can match menus and menu items. * @param recursive if set to true, will find depth-first in sub-menus as well. * @return all menu items in the specified shell that match the matcher. */ public List<MenuItem> findMenus(final Shell shell, Matcher<MenuItem> matcher, boolean recursive) { LinkedHashSet<MenuItem> result = new LinkedHashSet<MenuItem>(); result.addAll(findMenus(menuBar(shell), matcher, recursive)); return new ArrayList<MenuItem>(result); } /** * Gets the menu bar in the given shell. * * @param shell the shell. * @return the menu in the shell. * @see Shell#getMenuBar() */ protected Menu menuBar(final Shell shell) { return UIThreadRunnable.syncExec(display, new WidgetResult<Menu>() { public Menu run() { return shell.getMenuBar(); } }); } /** * Finds the menu item matching the given matcher in the given menu. If * recursive is set, it will attempt to find the menu item recursively * depth-first in each of the sub-menus that are found. * * @param menu the menu. * @param matcher the matcher that can match menu items. * @param recursive if set to true, will find depth-first in sub-menus as well. * @param index the index of the menu item, in case there are multiple matching menu items. * @return the menu item in the specified shell that matches the matcher, or null. * @since 2.4 */ public MenuItem findMenuItem(final Menu menu, final Matcher<MenuItem> matcher, final boolean recursive, final int index) { return UIThreadRunnable.syncExec(new WidgetResult<MenuItem>() { public MenuItem run() { return findMenuItemInternal(menu, matcher, recursive, new int[] { index } ); } }); } /** * Finds all menu items matching the given matcher in the given menu. If * recursive is set, it will attempt to find the menu items recursively in * each of the sub-menus that are found. * * @param menu the menu. * @param matcher the matcher that can match menu items. * @param recursive if set to true, will find depth-first in sub-menus as well. * @return all menu items in the specified menu that match the matcher. */ public List<MenuItem> findMenus(final Menu menu, final Matcher<MenuItem> matcher, final boolean recursive) { return UIThreadRunnable.syncExec(display, new ListResult<MenuItem>() { public List<MenuItem> run() { return findMenusInternal(menu, matcher, recursive); } }); } /** * Gets all of the shells in the current display. * * @return all shells in the display. * @see Display#getShells() */ protected Shell[] getShells() { return UIThreadRunnable.syncExec(display, new ArrayResult<Shell>() { public Shell[] run() { return display.getShells(); } }); } /** * @param menu * @param matcher * @param recursive * @param index * @return */ private MenuItem findMenuItemInternal(final Menu menu, final Matcher<MenuItem> matcher, final boolean recursive, final int[] index) { if (menu == null) { return null; } MenuItem[] items = menu.getItems(); for (MenuItem menuItem : items) { if (menuItem.isDisposed() || isSeparator(menuItem)) { continue; } if (matcher.matches(menuItem)) { if (index[0]-- > 0) { continue; } menuItem.notifyListeners(SWT.Arm, createEvent(menuItem)); Menu subMenu = menuItem.getMenu(); if (subMenu != null) { subMenu.notifyListeners(SWT.Show, createEvent(subMenu)); } return menuItem; } if (recursive) { Menu subMenu = menuItem.getMenu(); if (subMenu != null) { menuItem.notifyListeners(SWT.Arm, createEvent(menuItem)); subMenu.notifyListeners(SWT.Show, createEvent(subMenu)); MenuItem subMenuItem = findMenuItemInternal(subMenu, matcher, recursive, index); if (subMenuItem != null) { return subMenuItem; } subMenu.notifyListeners(SWT.Hide, createEvent(subMenu)); } } } return null; } /** * @param menu * @param matcher * @param recursive * @return */ private List<MenuItem> findMenusInternal(final Menu menu, final Matcher<MenuItem> matcher, final boolean recursive) { LinkedHashSet<MenuItem> result = new LinkedHashSet<MenuItem>(); if (menu != null) { MenuItem[] items = menu.getItems(); for (MenuItem menuItem : items) { if (menuItem.isDisposed() || isSeparator(menuItem)) { continue; } boolean matches = matcher.matches(menuItem); if (matches) { menuItem.notifyListeners(SWT.Arm, createEvent(menuItem)); Menu subMenu = menuItem.getMenu(); if (subMenu != null) { subMenu.notifyListeners(SWT.Show, createEvent(subMenu)); } result.add(menuItem); } if (recursive) { Menu subMenu = menuItem.getMenu(); if (subMenu != null) { if (!matches) { menuItem.notifyListeners(SWT.Arm, createEvent(menuItem)); subMenu.notifyListeners(SWT.Show, createEvent(subMenu)); } List<MenuItem> menuItems = findMenusInternal(subMenu, matcher, recursive); if (!menuItems.isEmpty()) { // Do not close menus which contain the item we're looking for - this destroys dynamic menu contributions // giving us the SWT MenuItem but without a E4 model attached (and therefore cannot be used). // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=469581 result.addAll(menuItems); } else if (!matches) { subMenu.notifyListeners(SWT.Hide, createEvent(subMenu)); } } } } } return new ArrayList<MenuItem>(result); } private boolean isSeparator(MenuItem menuItem) { // FIXME see https://bugs.eclipse.org/bugs/show_bug.cgi?id=208188 // FIXED > 20071101 https://bugs.eclipse.org/bugs/show_bug.cgi?id=208188#c2 return (menuItem.getStyle() & SWT.SEPARATOR) != 0; } }