| /******************************************************************************* |
| * Copyright (c) 2008, 2016 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 - Fix radio menu item click behavior (Bug 451126 & Bug 397649) |
| * - Improve SWTBot menu API and implementation (Bug 479091) |
| *******************************************************************************/ |
| package org.eclipse.swtbot.swt.finder.widgets; |
| |
| import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.withId; |
| import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.withMnemonic; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.swt.widgets.MenuItem; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.swtbot.swt.finder.SWTBot; |
| import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; |
| import org.eclipse.swtbot.swt.finder.results.BoolResult; |
| import org.eclipse.swtbot.swt.finder.results.ListResult; |
| import org.eclipse.swtbot.swt.finder.results.VoidResult; |
| import org.eclipse.swtbot.swt.finder.results.WidgetResult; |
| import org.eclipse.swtbot.swt.finder.utils.MessageFormat; |
| import org.eclipse.swtbot.swt.finder.utils.SWTUtils; |
| import org.eclipse.swtbot.swt.finder.waits.Conditions; |
| import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition; |
| import org.hamcrest.Matcher; |
| import org.hamcrest.SelfDescribing; |
| |
| /** |
| * SWTBot class representing a menu item. |
| * |
| * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> |
| * @version $Id$ |
| */ |
| public class SWTBotMenu extends AbstractSWTBot<MenuItem> { |
| |
| /** |
| * @param w the widget. |
| * @param description the description of the widget, this will be reported by {@link #toString()} |
| * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. |
| */ |
| public SWTBotMenu(MenuItem w, SelfDescribing description) throws WidgetNotFoundException { |
| super(w, description); |
| } |
| |
| /** |
| * @param w the widget. |
| * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. |
| */ |
| public SWTBotMenu(MenuItem w) throws WidgetNotFoundException { |
| this(w, null); |
| } |
| |
| /** |
| * Clicks on this menu item. Does nothing if this menu item has a menu. |
| */ |
| @Override |
| public SWTBotMenu click() { |
| if (hasMenu()) { |
| return this; |
| } |
| log.debug(MessageFormat.format("Clicking on {0}", this)); //$NON-NLS-1$ |
| waitForEnabled(); |
| asyncExec(new VoidResult() { |
| /* |
| * Send SWT.Hide and SWT.Selection events from the same UI runnable |
| * to ensure that the UI runnable triggered from the E4 model to |
| * dispose hidden menus is queued after the SWT.Selection event. |
| */ |
| public void run() { |
| hide(); |
| if (SWTUtils.hasStyle(widget, SWT.CHECK)) { |
| toggleCheckSelection(); |
| } else if (SWTUtils.hasStyle(widget, SWT.RADIO)) { |
| setRadioSelection(); |
| } |
| SWTBotMenu.this.notify(SWT.Selection); |
| }; |
| }); |
| syncExec(new VoidResult() { |
| public void run() { |
| // do nothing, just wait for sync. |
| } |
| }); |
| log.debug(MessageFormat.format("Clicked on {0}", this)); //$NON-NLS-1$ |
| return this; |
| } |
| |
| /** |
| * Gets the menu item matching the given text. |
| * <p> |
| * This is equivalent to calling menu(text); |
| * |
| * @param text the text on the menu item. |
| * @return the menu item that has the given text. |
| * @throws WidgetNotFoundException if the widget is not found. |
| */ |
| @Override |
| public SWTBotMenu contextMenu(String text) throws WidgetNotFoundException { |
| return menu(text); |
| } |
| |
| /** |
| * Recursively hide this menu item's parent menus until the root menu, |
| * including drop down menus and the pop up menu but excluding the menu bar. |
| * |
| * @return itself. |
| * @since 2.4 |
| */ |
| public SWTBotMenu hide() { |
| syncExec(new VoidResult() { |
| public void run() { |
| Menu menu = widget.getParent(); |
| /* the menu bar of a shell does not get hidden */ |
| while (menu instanceof Menu && ((menu.getStyle() & SWT.BAR) == 0)) { |
| Event event = createEvent(); |
| event.widget = menu; |
| menu.setVisible(false); |
| menu.notifyListeners(SWT.Hide, event); |
| menu = menu.getParentMenu(); |
| } |
| } |
| }); |
| return this; |
| } |
| |
| /** |
| * Toggle the selection of the check menu item. |
| */ |
| private void toggleCheckSelection() { |
| syncExec(new VoidResult() { |
| public void run() { |
| widget.setSelection(!widget.getSelection()); |
| } |
| }); |
| } |
| |
| /** |
| * Set the selection of the radio menu item and clear the selection of any |
| * other radio menu item in the same group. |
| */ |
| private void setRadioSelection() { |
| final SWTBotMenu otherSelectedRadioItem = otherSelectedRadioItem(); |
| if (otherSelectedRadioItem != null) { |
| otherSelectedRadioItem.notify(SWT.Deactivate); |
| asyncExec(new VoidResult() { |
| public void run() { |
| otherSelectedRadioItem.widget.setSelection(false); |
| } |
| }); |
| otherSelectedRadioItem.notify(SWT.Selection); |
| } |
| syncExec(new VoidResult() { |
| public void run() { |
| widget.setSelection(true); |
| } |
| }); |
| } |
| |
| private SWTBotMenu otherSelectedRadioItem() { |
| MenuItem other = syncExec(new WidgetResult<MenuItem>() { |
| public MenuItem run() { |
| if (hasStyle(widget.getParent(), SWT.NO_RADIO_GROUP)) |
| return null; |
| Widget[] siblings = SWTUtils.siblings(widget); |
| boolean ownGroup = false; |
| MenuItem selected = null; |
| for (Widget sibling : siblings) { |
| if (sibling == widget) { |
| ownGroup = true; |
| } else if (((sibling instanceof MenuItem) && hasStyle(sibling, SWT.RADIO))) { |
| if (((MenuItem) sibling).getSelection()) { |
| selected = (MenuItem) sibling; |
| } |
| } else if ((sibling instanceof MenuItem) && hasStyle(sibling, SWT.SEPARATOR)) { |
| ownGroup = false; |
| selected = null; |
| } |
| if (ownGroup && selected != null) { |
| return selected; |
| } |
| } |
| return null; |
| } |
| }); |
| |
| if (other != null) |
| return new SWTBotMenu(other); |
| return null; |
| } |
| |
| /** |
| * Gets the menu item matching the given text. |
| * |
| * @param text the text on the menu item that is to be found. |
| * @return the menu item that has the given text. |
| * @throws WidgetNotFoundException if the widget is not found. |
| */ |
| public SWTBotMenu menu(final String text) { |
| return menu(text, false, 0); |
| } |
| |
| /** |
| * Gets the menu item matching the given text path relative to this menu. It |
| * will attempt to recursively find the menu items in sequence in the |
| * matching sub-menus that are found. |
| * |
| * @param texts the texts on the menu items that are to be found. |
| * @return the menu item that has the given text. |
| * @throws WidgetNotFoundException if the widget is not found. |
| * @since 2.4 |
| */ |
| public SWTBotMenu menu(final String... texts) { |
| if (texts == null || texts.length == 0) { |
| return this; |
| } |
| SWTBotMenu menu = this; |
| for (String text : texts) { |
| menu = menu.menu(text, false, 0); |
| } |
| return menu; |
| } |
| |
| /** |
| * Gets the menu item matching the given text. 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 text the text on the menu item that is to be found. |
| * @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 that has the given text. |
| * @throws WidgetNotFoundException if the widget is not found. |
| * @since 2.4 |
| */ |
| public SWTBotMenu menu(final String text, final boolean recursive, final int index) throws WidgetNotFoundException { |
| final Matcher<MenuItem> matcher = withMnemonic(text); |
| return menu(matcher, recursive, index); |
| } |
| |
| /** |
| * Gets the menu item matching the given matcher. 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 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 that matches the matcher. |
| * @throws WidgetNotFoundException if the widget is not found. |
| * @since 2.4 |
| */ |
| public SWTBotMenu menu(Matcher<MenuItem> matcher, final boolean recursive, final int index) throws WidgetNotFoundException { |
| WaitForObjectCondition<MenuItem> waitForMenuItem = Conditions.waitForMenuItem(this, matcher, recursive, index); |
| new SWTBot().waitUntilWidgetAppears(waitForMenuItem); |
| return new SWTBotMenu(waitForMenuItem.get(0), matcher); |
| } |
| |
| /** |
| * Gets the menu item matching the given key/value pair in its widget data. |
| * 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 key the key of the id. |
| * @param value the value of the id. |
| * @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 that has the given key/value pair. |
| * @throws WidgetNotFoundException if the widget is not found. |
| * @since 2.4 |
| */ |
| public SWTBotMenu menuWithId(final String key, final String value, final boolean recursive, final int index) throws WidgetNotFoundException { |
| final Matcher<MenuItem> matcher = withId(key, value); |
| return menu(matcher, recursive, index); |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return syncExec(new BoolResult() { |
| public Boolean run() { |
| return widget.isEnabled(); |
| } |
| }); |
| } |
| |
| /** |
| * Returns true if this menu item has a menu, false otherwise. |
| * |
| * @return true if this menu item has a menu, false otherwise. |
| * @since 2.4 |
| */ |
| public boolean hasMenu() { |
| return syncExec(new BoolResult() { |
| public Boolean run() { |
| return widget.getMenu() != null; |
| } |
| }); |
| } |
| |
| /** |
| * Gets if this menu item is checked. |
| * |
| * @return <code>true</code> if the menu is checked, <code>false</code> otherwise. |
| * @see MenuItem#getSelection() |
| * @since 1.2 |
| */ |
| public boolean isChecked() { |
| return syncExec(new BoolResult() { |
| public Boolean run() { |
| return widget.getSelection(); |
| } |
| }); |
| } |
| |
| /** |
| * Returns the list of texts of this menu item's menu items, or an empty |
| * list if this menu item does not have a menu. The mnemonic character |
| * '&' and accelerator text are removed from each menu item's text. |
| * Separators are represented by empty strings. |
| * |
| * @return the list of menu item texts |
| * @since 2.5 |
| */ |
| public List<String> menuItems() { |
| return syncExec(new ListResult<String>() { |
| public List<String> run() { |
| List<String> items = new ArrayList<String>(); |
| Menu menu = widget.getMenu(); |
| if (menu != null) { |
| for (MenuItem menuItem : menu.getItems()) { |
| items.add(menuItem.getText().replaceAll("&", "").split("\t")[0]); |
| } |
| } |
| return items; |
| } |
| }); |
| } |
| } |