blob: b96e1fa533656e6360ee5b20626deb1c556ec942 [file] [log] [blame]
/*******************************************************************************
* 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
* Ketan Patel - https://bugs.eclipse.org/bugs/show_bug.cgi?id=259837
* Ralf Ebert www.ralfebert.de - (bug 271630) SWTBot Improved RCP / Workbench support
* Ingo Mohr - Bug 416859
* Lorenzo Bettini - https://bugs.eclipse.org/bugs/show_bug.cgi?id=435390
* Patrick Tasse - Improve SWTBot menu API and implementation (Bug 479091)
*******************************************************************************/
package org.eclipse.swtbot.eclipse.finder.widgets;
import static org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable.syncExec;
import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.Bullet;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.exceptions.QuickFixNotFoundException;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.keyboard.Keyboard;
import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes;
import org.eclipse.swtbot.swt.finder.matchers.AbstractMatcher;
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.Position;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
import org.eclipse.swtbot.swt.finder.widgets.TimeoutException;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.texteditor.ITextEditor;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.SelfDescribing;
import org.hamcrest.core.IsAnything;
/**
* This represents an eclipse editor item.
*
* @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com>
* @author Ralf Ebert www.ralfebert.de (bug 271630)
* @version $Id$
*/
public class SWTBotEclipseEditor extends SWTBotEditor {
private final SWTBotStyledText styledText;
private final Matcher<SWTBotTable> anyTable = new IsAnything<SWTBotTable>();
/**
* Constructs an instance of the given object.
*
* @param editorReference the editor reference.
* @param bot the instance of {@link SWTWorkbenchBot} which will be used to drive operations on behalf of this
* object.
* @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed.
* @since 2.0
*/
public SWTBotEclipseEditor(IEditorReference editorReference, SWTWorkbenchBot bot) throws WidgetNotFoundException {
super(editorReference, bot);
this.styledText = new SWTBotStyledText(widget());
}
/**
* Constructs an instance for the given editorReference.
*
* @param editorReference the part reference.
* @param bot the helper bot.
* @param description the description of the editor part.
*/
public SWTBotEclipseEditor(IEditorReference editorReference, SWTWorkbenchBot bot, SelfDescribing description) {
super(editorReference, bot, description);
this.styledText = new SWTBotStyledText(widget());
}
private StyledText widget() {
List<? extends Widget> findWidgets = findWidgets(widgetOfType(StyledText.class));
return (StyledText) findWidgets.get(findWidgets.size() - 1);
}
/**
* @return the styledText
*/
public SWTBotStyledText getStyledText() {
return styledText;
}
/**
* @return the editor reference for this view.
* @deprecated use {@link SWTBotWorkbenchPart#getReference()} instead
*/
public IEditorReference getEditorReference() {
return getReference();
}
/**
* Applys a quick fix item at the given index.
*
* @param quickFixIndex the index of the quickfix item to apply.
* @throws WidgetNotFoundException if the quickfix could not be found.
*/
public void quickfix(int quickFixIndex) {
WaitForObjectCondition<SWTBotTable> quickFixTableCondition = quickFixAppears(anyTable);
waitUntil(quickFixTableCondition);
SWTBotTable quickFixTable = quickFixTableCondition.get(0);
selectProposal(quickFixTable, quickFixIndex);
}
/**
* Applys a quick fix item with the given name.
*
* @param quickFixName the name of the quick fix to apply.
*/
public void quickfix(String quickFixName) {
WaitForObjectCondition<SWTBotTable> quickFixTable = quickFixAppears(tableWithRow(quickFixName));
waitUntil(quickFixTable);
selectProposal(quickFixTable.get(0), quickFixName);
}
/**
* Finds all the quickfixes in the quickfix list.
*
* @return the list of all available quickfixes.
* @since 1.2
*/
public List<String> getQuickFixes() {
WaitForObjectCondition<SWTBotTable> quickFixTableCondition = quickFixAppears(anyTable);
waitUntil(quickFixTableCondition);
SWTBotTable quickFixTable = quickFixTableCondition.get(0);
List<String> proposals = getRows(quickFixTable);
makeProposalsDisappear();
return proposals;
}
/**
* Gets the quick fix item count.
*
* @return the number of quickfix items in the quickfix proposals.
* @since 1.2
*/
public int getQuickfixListItemCount() {
WaitForObjectCondition<SWTBotTable> quickFixTableCondition = quickFixAppears(anyTable);
waitUntil(quickFixTableCondition);
SWTBotTable quickFixTable = quickFixTableCondition.get(0);
return quickFixTable.rowCount();
}
/**
* Attempst to applys the quick fix.
* <p>
* FIXME: this needs a lot of optimization.
* </p>
*
* @param proposalTable the table containing the quickfix.
* @param proposalText the name of the quickfix to apply.
*/
private void selectProposal(SWTBotTable proposalTable, String proposalText) {
log.debug(MessageFormat.format("Trying to select proposal {0}", proposalText)); //$NON-NLS-1$
if (proposalTable.containsItem(proposalText)) {
selectProposal(proposalTable, proposalTable.indexOf(proposalText));
return;
}
throw new QuickFixNotFoundException("Quickfix options not found. Giving up."); //$NON-NLS-1$
}
/**
* Applies the specified quickfix.
*
* @param proposalTable the table containing the quickfix.
* @param proposalIndex the index of the quickfix.
*/
private void selectProposal(final SWTBotTable proposalTable, final int proposalIndex) {
log.debug(MessageFormat.format("Trying to select proposal with index {0}", proposalIndex)); //$NON-NLS-1$
UIThreadRunnable.asyncExec(new VoidResult() {
public void run() {
Table table = proposalTable.widget;
log.debug(MessageFormat.format("Selecting row [{0}] {1} in {2}", proposalIndex, table.getItem(proposalIndex).getText(), //$NON-NLS-1$
table));
table.setSelection(proposalIndex);
Event event = new Event();
event.type = SWT.Selection;
event.widget = table;
event.item = table.getItem(proposalIndex);
table.notifyListeners(SWT.Selection, event);
table.notifyListeners(SWT.DefaultSelection, event);
}
});
}
/**
* Gets the quick fix table.
*
* @param proposalShell the shell containing the quickfixes.
* @return the table containing the quickfix.
*/
private SWTBotTable getProposalTable() {
log.debug("Finding table containing proposals.");
try {
Table table = bot.widget(widgetOfType(Table.class), activatePopupShell().widget);
SWTBotTable swtBotTable = new SWTBotTable(table);
log.debug(MessageFormat.format("Found table containing proposals -- {0}", getRows(swtBotTable)));
return swtBotTable;
} catch (Exception e) {
throw new QuickFixNotFoundException("Quickfix options not found. Giving up.", e); //$NON-NLS-1$
}
}
/**
* Gets the auto completion proposal matching the given text..
*
* @param insertText the proposal text to type before auto completing
* @return the list of proposals
* @throws TimeoutException if the autocomplete shell did not close in time.
* @since 1.2
*/
@SuppressWarnings("all")
public List<String> getAutoCompleteProposals(String insertText) {
typeText(insertText);
WaitForObjectCondition<SWTBotTable> autoCompleteAppears = autoCompleteAppears(tableWithRowIgnoringCase(insertText));
waitUntil(autoCompleteAppears);
final SWTBotTable autoCompleteTable = autoCompleteAppears.get(0);
List<String> proposals = getRows(autoCompleteTable);
makeProposalsDisappear();
return proposals;
}
/**
* Auto completes the given proposal.
*
* @param insertText the text to be inserted before activating the auto-complete.
* @param proposalText the auto-completion proposal to select from the list.
*/
public void autoCompleteProposal(String insertText, String proposalText) {
typeText(insertText);
WaitForObjectCondition<SWTBotTable> autoCompleteTable = autoCompleteAppears(tableWithRow(proposalText));
waitUntil(autoCompleteTable);
selectProposal(autoCompleteTable.get(0), proposalText);
}
/**
* Gets the context menu item matching the given text in the editor.
*
* @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 menu with the specified text could not be found.
*/
public SWTBotMenu contextMenu(String text) throws WidgetNotFoundException {
return styledText.contextMenu().menu(text);
}
/**
* Gets the current position of the cursor. The returned position will contain a 0-based line and column.
*
* @return the position of the cursor.
* @see SWTBotStyledText#cursorPosition()
*/
public Position cursorPosition() {
return styledText.cursorPosition();
}
/**
* Gets if the object's widget is enabled.
*
* @return <code>true</code> if the widget is enabled.
* @see org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot#isEnabled()
*/
public boolean isEnabled() {
return styledText.isEnabled();
}
/**
* Gets the current selection.
*
* @return The selected string.
*/
public String getSelection() {
return styledText.getSelection();
}
/**
* Gets the style text.
*
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @return the {@link StyleRange} at the specified location
* @see SWTBotStyledText#getStyle(int, int)
*/
public StyleRange getStyle(int line, int column) {
return styledText.getStyle(line, column);
}
/**
* Gets the text of this object's widget.
*
* @return the text on the styledtext.
* @see org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot#getText()
*/
public String getText() {
return styledText.getText();
}
/**
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @param text the text to be typed at the specified location
* @see SWTBotStyledText#typeText(int, int, java.lang.String)
* @since 1.0
*/
public void typeText(int line, int column, String text) {
styledText.typeText(line, column, text);
}
/**
* @param text the text to be typed at the location of the caret. *
* @see SWTBotStyledText#typeText(java.lang.String)
* @since 1.0
*/
public void typeText(String text) {
styledText.typeText(text);
}
/**
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @param text the text to be inserted at the specified location
* @see SWTBotStyledText#insertText(int, int, java.lang.String)
*/
public void insertText(int line, int column, String text) {
styledText.insertText(line, column, text);
}
/**
* @param text the text to be inserted at the location of the caret.
* @see SWTBotStyledText#insertText(java.lang.String)
*/
public void insertText(String text) {
styledText.insertText(text);
}
/**
* @param text the text to be typed at the location of the caret.
* @param interval the interval between consecutive key strokes.
* @see SWTBotStyledText#typeText(java.lang.String, int)
* @since 1.0
*/
public void typeText(String text, int interval) {
styledText.typeText(text, interval);
}
/**
* Sets the caret at the specified location.
*
* @param position the position of the caret.
*/
public void navigateTo(Position position) {
styledText.navigateTo(position);
}
/**
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @see SWTBotStyledText#navigateTo(int, int)
*/
public void navigateTo(int line, int column) {
styledText.navigateTo(line, column);
}
/**
* Notifies of the keyboard event.
* <p>
* FIXME need some work for CTRL|SHIFT + 1 the 1 is to be sent as '!' in this case.
* </p>
*
* @param modificationKeys the modification keys.
* @param c the character.
* @see Event#character
* @see Event#stateMask
* @deprecated use {@link #pressShortcut(int, char)} instead. This api will be removed.
*/
public void notifyKeyboardEvent(int modificationKeys, char c) {
styledText.notifyKeyboardEvent(modificationKeys, c);
}
/**
* Notifies of keyboard event.
*
* @param modificationKeys the modification key.
* @param c the character.
* @param keyCode any special keys (function keys, arrow or navigation keys etc.)
* @see Event#keyCode
* @see Event#character
* @see Event#stateMask
* @deprecated use {@link #pressShortcut(int, int, char)} instead. This api will be removed.
*/
public void notifyKeyboardEvent(int modificationKeys, char c, int keyCode) {
styledText.notifyKeyboardEvent(modificationKeys, c, keyCode);
}
/**
* Presses the shortcut specified by the given keys.
*
* @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} |
* {@link SWT#COMMAND}.
* @param c the character.
* @see Keyboard#pressShortcut(KeyStroke...)
* @see Keystrokes#toKeys(int, char)
*/
public void pressShortcut(int modificationKeys, char c) {
styledText.pressShortcut(modificationKeys, c);
}
/**
* Presses the shortcut specified by the given keys.
*
* @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} |
* {@link SWT#COMMAND}.
* @param keyCode the keyCode, these may be special keys like F1-F12, or navigation keys like HOME, PAGE_UP
* @param c the character
* @see Keystrokes#toKeys(int, char)
*/
public void pressShortcut(int modificationKeys, int keyCode, char c) {
styledText.pressShortcut(modificationKeys, keyCode, c);
}
/**
* Presses the shortcut specified by the given keys.
*
* @param keys the keys to press
* @see Keyboard#pressShortcut(KeyStroke...)
* @see Keystrokes
*/
public void pressShortcut(KeyStroke... keys) {
styledText.pressShortcut(keys);
}
/**
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @param length the length of the selection.
* @see SWTBotStyledText#selectRange(int, int, int)
*/
public void selectRange(int line, int column, int length) {
styledText.selectRange(line, column, length);
}
/**
* @param line the line number to select, 0 based.
* @see SWTBotStyledText#selectLine(int)
* @since 1.1
*/
public void selectLine(int line) {
styledText.selectLine(line);
}
/**
* Selects the text on the current line.
*
* @see SWTBotStyledText#selectCurrentLine()
* @since 1.1
*/
public void selectCurrentLine() {
styledText.selectCurrentLine();
}
/**
* @see SWTBotStyledText#setFocus()
*/
@Override
public void setFocus() {
styledText.setFocus();
}
/**
* @param text the text to set.
* @see SWTBotStyledText#setText(java.lang.String)
*/
public void setText(String text) {
styledText.setText(text);
}
/**
* @return the bullet on the current line.
* @see SWTBotStyledText#getBulletOnCurrentLine()
*/
public Bullet getBulletOnCurrentLine() {
return styledText.getBulletOnCurrentLine();
}
/**
* @param line the line number, 0 based.
* @return the bullet on the given line.
* @see SWTBotStyledText#getBulletOnLine(int)
*/
public Bullet getBulletOnLine(int line) {
return styledText.getBulletOnLine(line);
}
/**
* @param line the line number, 0 based.
* @param column the column number, 0 based.
* @param length the length.
* @return the styles in the specified range.
* @see SWTBotStyledText#getStyles(int, int, int)
*/
public StyleRange[] getStyles(int line, int column, int length) {
return styledText.getStyles(line, column, length);
}
/**
* @return the text on the current line, without the line delimiters.
* @see SWTBotStyledText#getTextOnCurrentLine()
*/
public String getTextOnCurrentLine() {
return styledText.getTextOnCurrentLine();
}
/**
* Get the text on line number given as parameter.
* Note that in eclipse editors, folding has no incidence on line numbers.
* @param line the line number, 0 based.
* @return the text on the given line number, without the line delimiters.
* @see SWTBotStyledText#getTextOnLine(int)
*/
public String getTextOnLine(int line) {
return styledText.getTextOnLine(line);
}
/**
* @return <code>true</code> if the styledText has a bullet on the given line, <code>false</code> otherwise.
* @see SWTBotStyledText#hasBulletOnCurrentLine()
*/
public boolean hasBulletOnCurrentLine() {
return styledText.hasBulletOnCurrentLine();
}
/**
* @param line the line number, 0 based.
* @return <code>true</code> if the styledText has a bullet on the given line, <code>false</code> otherwise.
* @see SWTBotStyledText#hasBulletOnLine(int)
*/
public boolean hasBulletOnLine(int line) {
return styledText.hasBulletOnLine(line);
}
/**
* Gets the background color of the widget.
*
* @return the background color on the widget, or <code>null</code> if the widget is not an instance of
* {@link Control}.
* @since 1.3
* @see org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot#backgroundColor()
*/
public Color backgroundColor() {
return styledText.backgroundColor();
}
/**
* Gets the foreground color of the widget.
*
* @return the foreground color on the widget, or <code>null</code> if the widget is not an instance of
* {@link Control}.
* @since 1.3
* @see org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot#foregroundColor()
*/
public Color foregroundColor() {
return styledText.foregroundColor();
}
/**
* Gets the color of the background on the specified line.
*
* @param line the line number, 0 based.
* @return the RGB of the line background color of the specified line.
* @since 1.3
* @see SWTBotStyledText#getLineBackground(int)
*/
public RGB getLineBackground(int line) {
return styledText.getLineBackground(line);
}
/**
* Gets the number of lines in the {@link StyledText}.
*
* @return the number of lines in the {@link StyledText}.
*/
public int getLineCount(){
return styledText.getLineCount();
}
/**
* Gets all the lines in the editor.
*
* @return the lines in the editor.
*/
public List<String> getLines() {
return styledText.getLines();
}
/**
* Gets the tooltip of this object's widget.
*
* @return the tooltip on the widget.
* @see org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot#getToolTipText()
* @since 1.3
*/
public String getToolTipText() {
return styledText.getToolTipText();
}
private void makeProposalsDisappear() {
// clear away all content assists for next retry.
log.debug("Making proposals disappear.");
setFocus();
}
private Matcher<SWTBotTable> tableWithRow(final String itemText) {
return new AbstractMatcher<SWTBotTable>() {
@Override
protected boolean doMatch(Object item) {
return ((SWTBotTable) item).containsItem(itemText);
}
public void describeTo(Description description) {
description.appendText("table with item (").appendText(itemText).appendText(")");
}
};
}
private Matcher<SWTBotTable> tableWithRowIgnoringCase(final String itemText) {
final String lowerCaseText = itemText.toLowerCase();
return new AbstractMatcher<SWTBotTable>() {
@Override
protected boolean doMatch(Object item) {
List<String> rows = getRows((SWTBotTable) item);
for (String row : rows) {
if (row.toLowerCase().startsWith(lowerCaseText)) {
return true;
}
}
return false;
}
public void describeTo(Description description) {
description.appendText("table with item (").appendText(itemText).appendText(")");
}
};
}
private WaitForObjectCondition<SWTBotTable> quickFixAppears(Matcher<SWTBotTable> tableMatcher) {
return new WaitForObjectCondition<SWTBotTable>(tableMatcher) {
@Override
protected List<SWTBotTable> findMatches() {
try {
activateQuickFixShell();
SWTBotTable quickFixTable = getProposalTable();
if (matcher.matches(quickFixTable))
return Arrays.asList(quickFixTable);
} catch (Throwable e) {
makeProposalsDisappear();
}
return null;
}
public String getFailureMessage() {
return "Could not find auto complete proposal using matcher " + matcher;
}
};
}
/**
* This activates the popup shell.
*
* @return The shell.
*/
private SWTBotShell activatePopupShell() {
log.debug("Activating quickfix shell."); //$NON-NLS-1$
try {
Shell mainWindow = syncExec(new WidgetResult<Shell>() {
public Shell run() {
return styledText.widget.getShell();
}
});
final List<Shell> shells = bot.shells("", mainWindow);
Shell widgetShell = syncExec(new WidgetResult<Shell>() {
public Shell run() {
for(int j=0; j<shells.size(); j++) {
Shell s = shells.get(j);
Control[] children = s.getChildren();
for (int i = 0; i < children.length; i++) {
//Select shell which has content assist table
if(children[i] instanceof Table) {
return s;
}
}
}
return shells.get(0);
}
});
SWTBotShell shell = new SWTBotShell(widgetShell);
shell.activate();
log.debug("Activated quickfix shell."); //$NON-NLS-1$
return shell;
} catch (Exception e) {
throw new QuickFixNotFoundException("Quickfix popup not found. Giving up.", e); //$NON-NLS-1$
}
}
private WaitForObjectCondition<SWTBotTable> autoCompleteAppears(Matcher<SWTBotTable> tableMatcher) {
return new WaitForObjectCondition<SWTBotTable>(tableMatcher) {
@Override
protected List<SWTBotTable> findMatches() {
try {
activateAutoCompleteShell();
SWTBotTable autoCompleteTable = getProposalTable();
if (matcher.matches(autoCompleteTable)) {
SWTBotEclipseEditor.this.log.debug("matched table, returning");
return Arrays.asList(autoCompleteTable);
}
} catch (Throwable e) {
makeProposalsDisappear();
}
return null;
}
public String getFailureMessage() {
return "Could not find auto complete proposal using matcher " + matcher;
}
};
}
private void activateAutoCompleteShell() {
invokeAction("ContentAssistProposal");
}
private void invokeAction(final String actionId) {
final IAction action = ((ITextEditor) partReference.getEditor(false)).getAction(actionId);
syncExec(new VoidResult() {
public void run() {
log.debug(MessageFormat.format("Activating action with id {0}", actionId));
action.run();
}
});
}
private List<String> getRows(SWTBotTable table) {
int rowCount = table.rowCount();
List<String> result = new ArrayList<String>();
for (int i = 0; i < rowCount; i++)
result.add(table.cell(i, 0));
return result;
}
private void activateQuickFixShell() {
invokeAction("QuickAssist");
}
private void waitUntil(WaitForObjectCondition<SWTBotTable> table) {
bot.waitUntil(table, SWTBotPreferences.TIMEOUT);
}
}