/* * Copyright 2014 Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.tests.swtbot.model; import com.google.dart.tools.internal.corext.refactoring.util.ReflectionUtils; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.bindings.keys.ParseException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; 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.widgets.SWTBotEclipseEditor; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.matchers.WidgetOfType; import org.eclipse.swtbot.swt.finder.results.IntResult; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.hamcrest.Matcher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; /** * Model a code editor of Dart Editor. */ @SuppressWarnings("restriction") public class TextBotEditor extends AbstractBotView { private static KeyStroke keyM1; private static KeyStroke keyA; private static KeyStroke keyF; private static KeyStroke keyS; private static KeyStroke keyZ; static { try { // Apparently there is no platform-independent method to construct KeyStrokes for modifiers // without going through this indirection. keyM1 = KeyStroke.getInstance("M1+F"); keyM1 = KeyStroke.getInstance(keyM1.getModifierKeys(), KeyStroke.NO_KEY); keyA = KeyStroke.getInstance("A"); keyF = KeyStroke.getInstance("F"); keyS = KeyStroke.getInstance("S"); keyZ = KeyStroke.getInstance("Z"); } catch (ParseException e) { // Won't happen } } private final String title; public TextBotEditor(SWTWorkbenchBot bot, String title) { super(bot); this.title = title; } public void clickHyperlinkAt(int line, int col) { final SWTBotEclipseEditor typist = editor(); typist.navigateTo(line, col); final Widget widget = typist.getStyledText().widget; final int x = measureLineToColumn(line, col); final int y = convertLineToVerticalOffset(line) + 5; int cmdKey = keyM1.getModifierKeys(); notify(SWT.MouseEnter, widget); notify(SWT.MouseMove, widget); notify(SWT.Activate, widget); notify(SWT.FocusIn, widget); // in order for MouseMove to trigger hyperlink detection the physical mouse location must // be set to the same point as (x,y) UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { typist.bot().getDisplay().setCursorLocation(((Composite) widget).toDisplay(x, y)); } }); notify(SWT.MouseMove, createMouseEvent(x, y, 0, cmdKey, 0), widget); notify(SWT.MouseHover, createMouseEvent(x, y, 0, cmdKey, 0), widget); waitForAsyncDrain(); notify(SWT.MouseDown, createMouseEvent(x, y, 1, cmdKey, 1), widget); notify(SWT.MouseUp, createMouseEvent(x, y, 1, cmdKey, 1), widget); waitForAnalysis(); } public CompletionProposalsBotView completionList() { waitForAnalysis(); waitMillis(3000); // when will it end? List<? extends Table> w = topTables(); assertEquals(1, w.size()); // we're going to go ahead and assume there is only one table whose parent is a shell and // that said table is the completion list return new CompletionProposalsBotView(bot, new SWTBotTable(w.get(0))); } /** * Double-click on the text at given coordinates. Assumes text on given line has no font changes. * * @param x the x co-ordinate of the click (1-based) * @param y the y co-ordinate of the click (1-based) */ public void doubleClick(int line, int col, boolean... focused) { doubleClickXY(measureLineToColumn(line, col), convertLineToVerticalOffset(line) - 5); } /** * Return the SWTBotEclipseEditor for this editor pane. * * @return the SWTBotEclipseEditor */ public SWTBotEclipseEditor editor() { return bot.editorByTitle(title).toTextEditor(); } /** * Use the Find Text panel to find all occurrences of the given text. * * @param text * @return the bot that controls the find-text panel */ public FindTextBotView findText(String text) { final SWTBotEclipseEditor editor = editor(); UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { editor.pressShortcut(keyM1, keyF); } }); waitForAnalysis(); FindTextBotView finder = new FindTextBotView(bot); finder.findText(text); waitMillis(500); return finder; } public ExtractMethodBotView openExtractMethodWizard() { waitForAsyncDrain(); editor().getStyledText().contextMenu("Extract Method...").click(); waitForAnalysis(); ExtractMethodBotView wizard = new ExtractMethodBotView(bot); return wizard; } public InlineMethodBotView openInlineMethodWizard() { waitForAsyncDrain(); editor().getStyledText().contextMenu("Inline...").click(); waitForAnalysis(); InlineMethodBotView wizard = new InlineMethodBotView(bot); return wizard; } public void save() { final SWTBotEclipseEditor editor = editor(); UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { editor.pressShortcut(keyM1, keyS); } }); waitForAnalysis(); } /** * Set the selection to the given string. If the optional <code>delta</code> is given, rather than * setting the selection to a range, set it to the number of characters from the beginning of the * <code>selection</code> as given by <code>delta[0]</code>. * * @param selection the string to search for an select * @param delta an optional single integer that defines a position relative to the beginning of * <code>selection</code> which should become the cursor position * @return */ public SWTBotStyledText select(String selection, int... delta) { SWTBotEditor editor = bot.editorByTitle(title); editor.show(); SWTBotStyledText text = editor.bot().styledText(); String content = text.getText(); IDocument doc = new Document(content); FindReplaceDocumentAdapter finder = new FindReplaceDocumentAdapter(doc); try { IRegion found = finder.find(0, selection, true, true, false, false); int offset = found.getOffset(); int line = doc.getLineOfOffset(offset); int column = offset - doc.getLineInformationOfOffset(offset).getOffset(); if (delta.length > 0) { text.selectRange(line, column + delta[0], 0); } else { text.selectRange(line, column, selection.length()); } return text; } catch (BadLocationException ex) { fail(ex.getMessage()); throw new RuntimeException(ex); } } /** * Select everything in the editor and return the model of the text view. * * @return the SWTBotStyledText of the editor */ public SWTBotStyledText selectAll() { SWTBotEditor editorView = bot.editorByTitle(title); editorView.show(); SWTBotStyledText text = editorView.bot().styledText(); UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { editor().pressShortcut(keyM1, keyA); } }); return text; } /** * Return the currently-selected string. * * @return the selection */ public String selection() { SWTBotEditor editor = bot.editorByTitle(title); SWTBotStyledText text = editor.bot().styledText(); String selection = text.getSelection(); return selection; } /** * Set a breakpoint on the given <code>lineNo</code>. * * @param lineNo The 1-based line number to set a breakpoint */ public void setBreakPointOnLine(int lineNo) { Matcher<Canvas> matcher = WidgetOfType.widgetOfType(Canvas.class); List<? extends Canvas> all = editor().bot().widgets(matcher); final int y = convertLineToVerticalOffset(lineNo); for (Canvas w : all) { // There actually are two AnnotationRulerColumn's. It is almost impossible to distinguish // them. One toggles folding, the one we want sets breakpoints. if (w.getClass().getSimpleName().startsWith("AnnotationRulerColumn")) { final Canvas c = w; Object outer = ReflectionUtils.getFieldObject(c, "this$0"); if (outer.getClass().getSimpleName().startsWith("AnnotationRulerColumn")) { Event event = createMouseEvent(0, y, 1, SWT.NONE, 2); notify(SWT.MouseDoubleClick, event, w); break; } } } } /** * Undo the last edit using the keyboard shortcut. */ public void undo() { final SWTBotEclipseEditor editor = editor(); UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { editor.pressShortcut(keyM1, keyZ); } }); waitForAnalysis(); } /** * Get all the widgets that are accessible from the editor. * * @return a list of widgets */ public List<? extends Widget> widgets() { final Matcher<Widget> matcher = WidgetOfType.widgetOfType(Widget.class); return UIThreadRunnable.syncExec(new Result<List<? extends Widget>>() { @Override public List<? extends Widget> run() { return bot.widgets(matcher, ((Composite) editor().getWidget()).getParent()); } }); } @Override protected String viewName() { return title; } private int convertLineToVerticalOffset(final int lineNo) { // lineNo is 1-based final SWTBotEclipseEditor editor = editor(); return UIThreadRunnable.syncExec(new IntResult() { @Override public Integer run() { int height = editor.getStyledText().widget.getLineHeight(); return height * lineNo - 1; } }); } private void doubleClickXY(int x, int y) { SWTBotEclipseEditor editor = editor(); StyledText widget = editor.getStyledText().widget; notify(SWT.MouseEnter, widget); notify(SWT.MouseMove, widget); notify(SWT.Activate, widget); notify(SWT.FocusIn, widget); notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1), widget); // StyledText needs a MouseUp to clear previous selection notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.NONE, 1), widget); notify(SWT.Selection, createSelectionEvent(SWT.BUTTON1), widget); // StyledText does not respond to MouseDoubleClick; use MouseDown with count=2 notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 2), widget); notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.NONE, 1), widget); notify(SWT.MouseHover, widget); notify(SWT.MouseMove, widget); notify(SWT.MouseExit, widget); notify(SWT.Deactivate, widget); notify(SWT.FocusOut, widget); } @SuppressWarnings("unused") private IEditorReference editorReference() { // TODO for reference only; probably want to use SWTBotView return UIThreadRunnable.syncExec(new Result<IEditorReference>() { @Override public IEditorReference run() { IWorkbenchWindow bench = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IEditorReference[] refs = bench.getActivePage().getEditorReferences(); for (IEditorReference ref : refs) { if (title.equals(ref.getTitle())) { return ref; } } return null; } }); } private int measureLineToColumn(final int lineNo, final int colNo) { // lineNo, colNo are 1-based final SWTBotEclipseEditor editor = editor(); return UIThreadRunnable.syncExec(new IntResult() { @Override public Integer run() { StyledText text = editor.getStyledText().widget; GC gc = new GC(text.getShell()); gc.setFont(text.getFont()); String line = text.getLine(lineNo - 1); line = line.substring(0, colNo - 1); Point ext = gc.textExtent(line); gc.dispose(); return ext.x; } }); } private List<? extends Table> topTables() { final Matcher<Table> matcher = WidgetOfType.widgetOfType(Table.class); return UIThreadRunnable.syncExec(new Result<List<? extends Table>>() { @Override public List<? extends Table> run() { List<Shell> shells = bot.shells("", ((Composite) editor().getWidget()).getShell()); List<Table> tables = new ArrayList<Table>(); for (Shell s : shells) { try { tables.addAll(bot.widgets(matcher, s)); } catch (WidgetNotFoundException ex) { // do nothing -- one of the shells may have a table, the others do not } } List<Table> topTables = new ArrayList<Table>(); for (Table t : tables) { for (Shell x : shells) { if (t.getParent() == x) { topTables.add(t); } } } return topTables; } }); } }