/******************************************************************************* * Copyright (c) 2004, 2011 MAKE Technologies Inc 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: * MAKE Technologies Inc - initial API and implementation * Mariot Chauvin <mariot.chauvin@obeo.fr> - Improvements and bug fixes * Steve Monnier <steve.monnier@obeo.fr> - Improvements and bug fixes * Nathalie Lepine <nathalie.lepine@obeo.fr> - Improvements and bug fixes * Pascal Gelinas <pascal.gelinas @nuecho.com> - Improvements and bug fixes * Mickael Istria <mickael.istria@bonitasoft.com> - Improvements and bug fixes * Tim Kaiser <tim.kaiser@sap.com> - Improvements and bug fixes *******************************************************************************/ package org.eclipse.swtbot.eclipse.gef.finder.widgets; import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.regex.Pattern; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.draw2d.FigureCanvas; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.LightweightSystem; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.text.TextFlow; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.EditDomain; import org.eclipse.gef.EditPart; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.GraphicalViewer; import org.eclipse.gef.palette.PaletteEntry; import org.eclipse.gef.palette.ToolEntry; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Text; import org.eclipse.swtbot.eclipse.gef.finder.finders.PaletteFinder; import org.eclipse.swtbot.eclipse.gef.finder.matchers.ToolEntryLabelMatcher; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.hamcrest.Matcher; /** * represent a graphical viewer that uses the GEF framework. * * @author David Green */ public class SWTBotGefViewer { protected GraphicalViewer graphicalViewer; protected EditDomain editDomain; protected SWTBotGefFigureCanvas canvas; private Map<EditPart, SWTBotGefEditPart> editPartMapping = new WeakHashMap<EditPart, SWTBotGefEditPart>(); /** * Create a new bot GEF graphical viewer instance. * * @param graphicalViewer the graphical viewer to wrap * @throws WidgetNotFoundException if graphical viewer is null */ public SWTBotGefViewer(final GraphicalViewer graphicalViewer) throws WidgetNotFoundException { if (graphicalViewer == null) { throw new WidgetNotFoundException("The graphical viewer is null."); } this.graphicalViewer = graphicalViewer; init(); } public SWTBot bot() { return new SWTBot(canvas.widget); } /** * clear the cache of edit parts */ public void clear() { editPartMapping.clear(); } protected void init() throws WidgetNotFoundException { UIThreadRunnable.syncExec(new VoidResult() { public void run() { final Control control = graphicalViewer.getControl(); if (control instanceof FigureCanvas) { canvas = new SWTBotGefFigureCanvas((FigureCanvas) control); } else if (control instanceof Canvas) { if (control instanceof IAdaptable) { IAdaptable adaptable = (IAdaptable) control; Object adapter = adaptable.getAdapter(LightweightSystem.class); if (adapter instanceof LightweightSystem) { canvas = new SWTBotGefFigureCanvas((Canvas) control, (LightweightSystem) adapter); } } } editDomain = graphicalViewer.getEditDomain(); } }); if (graphicalViewer == null) { throw new WidgetNotFoundException("Editor does not adapt to a GraphicalViewer"); } } public SWTBotGefEditPart mainEditPart() throws WidgetNotFoundException { List<SWTBotGefEditPart> children = rootEditPart().children(); if (children.size() != 1) { throw new WidgetNotFoundException(String.format("Root edit part has %s children", children.size())); } return children.get(0); } /** * retrieve the root edit part. * * @return the root edit part * @throws WidgetNotFoundException if root edit part could not be found * @see {@link GraphicalViewer#getRootEditPart()} */ public SWTBotGefEditPart rootEditPart() throws WidgetNotFoundException { Object o = UIThreadRunnable.syncExec(new Result<Object>() { public Object run() { return createEditPart(graphicalViewer.getRootEditPart()); } }); if (o instanceof WidgetNotFoundException) { throw (WidgetNotFoundException) o; } return (SWTBotGefEditPart) o; } /** * Get the selected edit parts. * @return the selected edit parts */ @SuppressWarnings("unchecked") public List<SWTBotGefEditPart> selectedEditParts() { List<SWTBotGefEditPart> toReturn = new ArrayList<SWTBotGefEditPart>(); List<EditPart> parts = graphicalViewer.getSelectedEditParts(); for (EditPart editPart : parts) { toReturn.add(createEditPart(editPart)); } return toReturn; } /** * lazily creates a {@link SWTBotGefEditPart} if this edit part does not exist yet. If an instance encapsulating the * specified edit part has been created before, that instance is returned. * * @param part the edit part to create a {@link SWTBotGefEditPart} for * @return the created {@link SWTBotGefEditPart} */ protected SWTBotGefEditPart createEditPart(final EditPart part) { SWTBotGefEditPart editPart = editPartMapping.get(part); if (editPart == null) { if (part instanceof ConnectionEditPart) { editPart = new SWTBotGefConnectionEditPart(this, (ConnectionEditPart) part); } else { editPart = new SWTBotGefEditPart(this, part); } editPartMapping.put(part, editPart); } return editPart; } /** * lazily creates a {@link SWTBotGefConnectionEditPart} if this edit part does not exist yet. If an instance * encapsulating the specified edit part has been created before, that instance is returned. * * @param part a connection edit part connecting graphical nodes * @return a {@link SWTBotGefConnectionEditPart} encapsulating the connection edit part */ protected SWTBotGefConnectionEditPart createEditPart(ConnectionEditPart part) { return (SWTBotGefConnectionEditPart) createEditPart((EditPart) part); } /** * Activate the default tool. */ public void activateDefaultTool() { UIThreadRunnable.syncExec(new VoidResult() { public void run() { final EditDomain editDomain = getEditDomain(); editDomain.setActiveTool(editDomain.getDefaultTool()); } }); } /** * Activate the tool with the specified label. If there is many tools with the same label the first one will be * used. See {@link SWTBotGefViewer#activateTool(String, int)} * * @param label the label of the tool to activate * @return the editor bot * @throws WidgetNotFoundException if the tool with label specified could not be found */ public SWTBotGefViewer activateTool(final String label) throws WidgetNotFoundException { activateTool(Pattern.compile(Pattern.quote(label)), 0); return this; } /** * Activate the tool with the specified label and the specified index. This method should be used only if there is * many tools with the same label. See {@link SWTBotGefViewer#activateTool(String)} * * @param label the label of the tool to activate * @param index the index to use in order to make the selection. * @return the editor bot * @throws WidgetNotFoundException if the tool with label specified could not be found */ public SWTBotGefViewer activateTool(final String label, int index) throws WidgetNotFoundException { activateTool(Pattern.compile(Pattern.quote(label)), index); return this; } private SWTBotGefViewer activateTool(final Pattern labelMatcher, final int index) throws WidgetNotFoundException { final WidgetNotFoundException[] exception = new WidgetNotFoundException[1]; UIThreadRunnable.syncExec(new VoidResult() { public void run() { final EditDomain editDomain = getEditDomain(); final List<PaletteEntry> entries = new PaletteFinder(editDomain).findEntries(new ToolEntryLabelMatcher(labelMatcher)); if (entries.size() > 0) { final PaletteEntry paletteEntry = entries.get(index); if (paletteEntry instanceof ToolEntry) { editDomain.getPaletteViewer().setActiveTool((ToolEntry) paletteEntry); } else { exception[0] = new WidgetNotFoundException(String.format("%s is not a tool entry, it's a %s", labelMatcher .toString(), paletteEntry.getClass().getName())); } } else { exception[0] = new WidgetNotFoundException(labelMatcher.toString()); } } }); if (exception[0] != null) { throw exception[0]; } return this; } /** * call on UI thread only */ private EditDomain getEditDomain() { return editDomain; } /** * type the given text into the graphical editor, presuming that it is already in 'direct edit' mode. * * @param text the text to type. * @throws WidgetNotFoundException */ public void directEditType(String text) throws WidgetNotFoundException { /* * we use 'bot()' and not 'bot' to scope the widget search to the editor. Otherwise if another widget of the * same type is present in the workspace and is found first, the code after will fail. * We specify the parent widget, to narrow the search to the supplied canvas. */ /* by using SWTBot#widgets() we get the added benefit of an implicit wait condition */ List<? extends Text> controls = bot().widgets(widgetOfType(Text.class), canvas.widget); if (controls.size() == 1) { final Text textControl = controls.get(0); canvas.typeText(textControl, text); } else { throw new WidgetNotFoundException(String.format( "Expected to find one text control, but found %s. Is the editor in direct-edit mode?", controls.size())); } } /** * @param matcher the matcher that matches on {@link org.eclipse.gef.EditPart} * @return a collection of {@link SWTBotGefEditPart} * @throws WidgetNotFoundException */ public List<SWTBotGefEditPart> editParts(Matcher<? extends EditPart> matcher) throws WidgetNotFoundException { return rootEditPart().descendants(matcher); } /** * Get the canvas to do low-level operations. * * @return the canvas */ protected SWTBotGefFigureCanvas getCanvas() { return canvas; } protected Control getControl() { return graphicalViewer.getControl(); } /** * Get active tool. * * @return the active tool */ public ToolEntry getActiveTool() { return editDomain.getPaletteViewer().getActiveTool(); } /** * select this edit part as a single selection */ public SWTBotGefViewer select(SWTBotGefEditPart... parts) { return select(Arrays.asList(parts)); } /** * select this edit part as a single selection */ public SWTBotGefViewer select(final Collection<SWTBotGefEditPart> parts) { UIThreadRunnable.syncExec(new VoidResult() { public void run() { List<EditPart> selectParts = new ArrayList<EditPart>(parts.size()); for (SWTBotGefEditPart p : parts) { selectParts.add(p.part); } graphicalViewer.setFocus(selectParts.get(0)); graphicalViewer.setSelection(new StructuredSelection(selectParts)); } }); return this; } public SWTBotGefViewer clickContextMenu(String text) throws WidgetNotFoundException { new SWTBotGefContextMenu(getControl(), text).click(); return this; } /** * Click on the editor at the specified location. * * @param xPosition the x relative position * @param yPosition the y relative position */ public void click(final int xPosition, final int yPosition) { canvas.mouseMoveLeftClick(xPosition, yPosition); } /** * Click on the specified edit part at the top left hand corner of its bounds. * * @param editPart the edit part to click on */ public void click(final SWTBotGefEditPart editPart) { Rectangle bounds = getAbsoluteBounds(editPart); click(bounds.x, bounds.y); } /** * Click on the edit part which owns the specified label at the top left hand corner of its bounds. * * @param label the label to retrieve edit part to click on */ public void click(final String label) { SWTBotGefEditPart selectedEP = getEditPart(label); if (selectedEP == null) { throw new WidgetNotFoundException(String.format("Expected to find widget %s", label)); } click(selectedEP); } /** * Double click on the editor at the specified location. * * @param xPosition the x relative position * @param yPosition the y relative position */ public void doubleClick(final int xPosition, final int yPosition) { canvas.mouseMoveDoubleClick(xPosition, yPosition); } /** * Double click on the edit part which owns the specified label at the top left hand corner (with an offset) of its * bounds. * * @param editPart the edit part to double click on */ public void doubleClick(final SWTBotGefEditPart editPart) { Rectangle bounds = getAbsoluteBounds(editPart); /* * Note that a move is required before double clicking in order to update the mouse cursor with the target * editpart. As we can not double click on the corner, we move the double click position */ int move = 3; doubleClick(bounds.x, bounds.y + move); } /** * Double click on the edit part which owns the specified label at the top left hand corner (with an offset) of its * bounds. * * @param label the label to retrieve edit part to double click on */ public void doubleClick(final String label) { SWTBotGefEditPart selectedEP = getEditPart(label); if (selectedEP == null) { throw new WidgetNotFoundException(String.format("Expected to find widget %s", label)); } doubleClick(selectedEP); } /** * Drag and drop from the specified to the specified location. * * @param toXPosition the x relative location * @param toYPosition the y relative location */ public void drag(final int fromXPosition, final int fromYPosition, final int toXPosition, final int toYPosition) { canvas.mouseDrag(fromXPosition, fromYPosition, toXPosition, toYPosition); } /** * Drag and drop the specified edit part to the specified location. * * @param editPart the edit part to drag and drop * @param toXPosition the x relative location * @param toYPosition the y relative location */ public void drag(final SWTBotGefEditPart editPart, final int toXPosition, final int toYPosition) { Rectangle bounds = getAbsoluteBounds(editPart); /* * We should increment drag location to avoid a resize. 7 comes from SquareHandle#DEFAULT_HANDLE_SIZE and we * divided by 2 as AbstractHandle#getAccessibleLocation do that by default */ int offset = 7 / 2 + 1; drag(bounds.x + offset, bounds.y + offset, toXPosition + offset, toYPosition + offset); } /** * Get absolute bounds of the edit part. * * @param editPart edit part * @return the absolute bounds */ private Rectangle getAbsoluteBounds(final SWTBotGefEditPart editPart) { IFigure figure = ((GraphicalEditPart) editPart.part()).getFigure(); Rectangle bounds = figure.getBounds().getCopy(); figure.translateToAbsolute(bounds); return bounds; } /** * Drag and drop the edit part which owns the specified label to the specified location * * @param label the label to retrieve the edit part to drag and drop * @param toXPosition the x relative position * @param toYPosition the y relative position */ public void drag(final String label, final int toXPosition, final int toYPosition) { SWTBotGefEditPart selectedEP = getEditPart(label); if (selectedEP == null) { throw new WidgetNotFoundException(String.format("Expected to find widget %s", label)); } drag(selectedEP, toXPosition, toYPosition); } /** * select the edit part with the label as a single selection. */ public SWTBotGefViewer select(String label) { SWTBotGefEditPart selectedEP = getEditPart(label); if (selectedEP == null) { throw new WidgetNotFoundException(String.format("Expected to find widget %s", label)); } List<SWTBotGefEditPart> editParts = new ArrayList<SWTBotGefEditPart>(); editParts.add(selectedEP); return select(selectedEP); } /** * get this edit part with the label as a single selection. */ public SWTBotGefEditPart getEditPart(String label) { List<SWTBotGefEditPart> allEditParts = mainEditPart().children(); allEditParts.addAll(mainEditPart().sourceConnections()); return getEditpart(label, allEditParts); } // FIXME should moved in a finder @Deprecated /* * * get this edit part with the label as a single selection */ public SWTBotGefEditPart getEditpart(String label, List<SWTBotGefEditPart> allEditParts) { for (SWTBotGefEditPart child : allEditParts) { IFigure figure = ((GraphicalEditPart) child.part()).getFigure(); if (isLabel(figure, label)) { return child; } SWTBotGefEditPart childEditPart = getEditPart(child, label); if (childEditPart != null) { return childEditPart; } if (findLabelFigure(figure, label)) return child; } return null; } /** * get this edit part with the label as a single selection */ private SWTBotGefEditPart getEditPart(SWTBotGefEditPart editPart, String label) { if (editPart.children().isEmpty() && findLabelFigure(((GraphicalEditPart) editPart.part()).getFigure(), label)) { return editPart; } List<SWTBotGefEditPart> allEditParts = editPart.children(); allEditParts.addAll(editPart.sourceConnections()); return getEditpart(label, allEditParts); } // FIXME should moved in a finder /** * @return if the figure is a label */ private boolean isLabel(IFigure figure, String label) { // case 1 : gef label if ((figure instanceof Label && ((Label) figure).getText().equals(label))) { return true; } // case 2 : no gef label if ((figure instanceof TextFlow && ((TextFlow) figure).getText().equals(label))) { return true; } return false; } // FIXME should moved in a finder /** * @return if the figure or all its children contain the label */ private boolean findLabelFigure(IFigure figure, String label) { if (isLabel(figure, label)) { return true; } for (Object figureChild : figure.getChildren()) { if (isLabel((IFigure) figureChild, label) || findLabelFigure((IFigure) figureChild, label)) { return true; } } return false; } }