/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.rc.rcp.e3.gef.inspector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.jubula.communication.internal.message.InspectorComponentSelectedMessage;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.listener.IAutListenerAppender;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.util.PropertyUtil;
import org.eclipse.jubula.rc.rcp.e3.gef.factory.DefaultEditPartAdapterFactory;
import org.eclipse.jubula.rc.rcp.e3.gef.identifier.IEditPartIdentifier;
import org.eclipse.jubula.rc.rcp.e3.gef.listener.GefPartListener;
import org.eclipse.jubula.rc.rcp.e3.gef.util.FigureCanvasUtil;
import org.eclipse.jubula.tools.internal.exception.CommunicationException;
import org.eclipse.jubula.tools.internal.objects.ComponentIdentifier;
import org.eclipse.jubula.tools.internal.objects.IComponentIdentifier;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.PlatformUI;
/**
* Adds listeners for the Inspector function. These listeners will only
* interact with GEF components.
*
* @author BREDEX GmbH
* @created Jun 11, 2009
*/
public class GefInspectorListenerAppender implements IAutListenerAppender {
/** the logger */
private static final AutServerLogger LOG =
new AutServerLogger(GefInspectorListenerAppender.class);
/**
* Responsible for adding highlighters to controls and managing those
* highlighters.
*
* @author BREDEX GmbH
* @created Jun 29, 2009
*/
private static class FigureHighlightAppender implements Listener {
/** mapping from control to figure highlighter */
private Map<FigureCanvas, FigureHighlighter> m_canvasToListener =
new HashMap<FigureCanvas, FigureHighlighter>();
/**
* {@inheritDoc}
*/
public void handleEvent(Event event) {
if (event.widget instanceof FigureCanvas) {
FigureCanvas canvas = (FigureCanvas)event.widget;
if (event.type == SWT.MouseEnter) {
handleMouseEnter(canvas);
}
}
}
/**
* Removes all listeners added by this appender.
*/
public void removeAddedListeners() {
Iterator<FigureCanvas> iter = m_canvasToListener
.keySet().iterator();
while (iter.hasNext()) {
Control control = iter.next();
if (!control.isDisposed()) {
FigureHighlighter highlighter =
m_canvasToListener.get(control);
highlighter.removeAddedListeners();
control.removeMouseMoveListener(highlighter);
}
}
m_canvasToListener.clear();
}
/**
* Adds a figure highlighter to the given canvas.
*
* @param canvas The figure canvas entered by the mouse.
*/
private void handleMouseEnter(final FigureCanvas canvas) {
if (m_canvasToListener.containsKey(canvas)) {
// Highlighter already registered.
return;
}
GraphicalViewer viewer = FigureCanvasUtil.getViewer(canvas);
if (viewer != null) {
if (viewer.getContents() instanceof GraphicalEditPart) {
FigureHighlighter highlighter =
new FigureHighlighter(viewer);
m_canvasToListener.put(canvas, highlighter);
canvas.addMouseMoveListener(highlighter);
}
}
}
}
/**
* Listens for a mouse click and:
* 1. deregisters itself and any other provider listeners, and
* 2. sends a "component selected" message
*
* if that mouse click was over a GEF component.
*
* @author BREDEX GmbH
* @created Jun 11, 2009
*/
private static class GefInspectorComponentSelectedListener
implements Listener {
/** the appender */
private FigureHighlightAppender m_highlightAppender;
/**
* Constructor
*
* @param highlightAppender The appender to use.
*/
public GefInspectorComponentSelectedListener(
FigureHighlightAppender highlightAppender) {
m_highlightAppender = highlightAppender;
}
/**
*
* @param editPartIdentifier Provides connection anchor IDs and
* locations.
* @param cursorLocation The location at which to search for an
* anchor point.
* @return the ID of the connection anchor at the given location, or
* <code>null</code> if there is no connection anchor at the
* given location.
*/
private String getConnectionAnchorId(
IEditPartIdentifier editPartIdentifier, Point cursorLocation) {
if (editPartIdentifier != null) {
Map<?, ?> anchorMap =
editPartIdentifier.getConnectionAnchors();
if (anchorMap != null) {
Iterator<?> iter = anchorMap.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object value = anchorMap.get(key);
if (key instanceof String
&& value instanceof ConnectionAnchor) {
Point refPoint =
((ConnectionAnchor)value).getReferencePoint();
// A click is recognized as being "within the
// bounds" of an anchor if it is within 3 pixels
// in any direction.
Rectangle refBounds = new Rectangle(
refPoint.x - 3, refPoint.y - 3, 7, 7);
if (refBounds.contains(cursorLocation)) {
return (String)key;
}
}
}
}
}
return null;
}
/**
*
* {@inheritDoc}
*/
public void handleEvent(Event event) {
Display display = event.display;
display.removeFilter(SWT.MouseDown, this);
display.removeFilter(SWT.MouseEnter, m_highlightAppender);
m_highlightAppender.removeAddedListeners();
event.doit = false;
event.type = SWT.None;
Widget selectedWidget = event.widget;
IComponentIdentifier compId = null;
if (!(selectedWidget instanceof FigureCanvas)) {
sendIdInfo(compId);
return;
}
FigureCanvas figureCanvas = (FigureCanvas)selectedWidget;
Composite parent = figureCanvas;
String testGefViewerDataKey =
GefPartListener.TEST_GEF_VIEWER_DATA_KEY;
while (parent != null && !(parent.getData(
testGefViewerDataKey) instanceof GraphicalViewer)) {
parent = parent.getParent();
}
if (parent == null) {
sendIdInfo(compId);
return;
}
Object gefData = parent.getData(testGefViewerDataKey);
if (gefData instanceof EditPartViewer) {
EditPartViewer viewer = (EditPartViewer)gefData;
Point cursorLocation = new Point(display.map(null,
viewer.getControl(),
display.getCursorLocation()));
EditPart editPart = viewer.findObjectAt(cursorLocation);
EditPart primaryEditPart = FigureCanvasUtil.getPrimaryEditPart(
editPart, viewer.getRootEditPart());
List<String> idStringList = Collections.EMPTY_LIST;
Map<String, String> properties = null;
if (primaryEditPart != null) {
idStringList = getPathToRoot(viewer.getRootEditPart(),
cursorLocation, primaryEditPart);
if (primaryEditPart instanceof GraphicalEditPart) {
GraphicalEditPart gep =
(GraphicalEditPart) primaryEditPart;
properties = PropertyUtil.getMapOfComponentProperties(
FigureCanvasUtil.findFigure(gep));
}
} else {
// No primary figure found.
// Check whether a tool was selected.
EditDomain editDomain = viewer.getEditDomain();
if (editDomain != null) {
PaletteViewer paletteViewer =
editDomain.getPaletteViewer();
if (paletteViewer != null) {
EditPart paletteEditPart =
paletteViewer.findObjectAt(new Point(
display.map(viewer.getControl(),
paletteViewer.getControl(),
cursorLocation.getSWTPoint())));
if (paletteEditPart != null) {
idStringList = getToolPathToRoot(
paletteViewer.getRootEditPart(),
paletteEditPart);
}
}
}
}
compId = createCompId(idStringList, properties);
}
sendIdInfo(compId);
}
/**
*
* @param editPart The edit part for which to find the path.
* @param root The root for <code>editPart</code>. This is used to
* avoid adding the root identifier to the returned list.
* @param cursorLocation The location to check for nearby connection
* anchors.
* @return a list containing the identifier of each edit part between
* <code>editPart</code> and its root. The first element in the
* list will be the identifier for a connection anchor if
* <code>cursorLocation</code> is near such an anchor.
*/
private List<String> getPathToRoot(RootEditPart root,
Point cursorLocation, EditPart editPart) {
List<String> idStringList = new ArrayList<String>();
EditPart currentEditPart = editPart;
// Check for connection anchor
String connectionId =
getConnectionAnchorId(
DefaultEditPartAdapterFactory.loadFigureIdentifier(
currentEditPart),
cursorLocation);
if (connectionId == null
&& currentEditPart instanceof ConnectionEditPart) {
ConnectionEditPart connEditPart =
(ConnectionEditPart)editPart;
EditPart srcEditPart = connEditPart.getSource();
EditPart targetEditPart = connEditPart.getTarget();
connectionId = getConnectionAnchorId(
DefaultEditPartAdapterFactory
.loadFigureIdentifier(srcEditPart),
cursorLocation);
if (connectionId != null) {
currentEditPart = srcEditPart;
} else {
connectionId = getConnectionAnchorId(
DefaultEditPartAdapterFactory
.loadFigureIdentifier(targetEditPart),
cursorLocation);
if (connectionId != null) {
currentEditPart = targetEditPart;
}
}
}
if (connectionId != null) {
idStringList.add(connectionId);
}
while (currentEditPart != root.getContents()
&& currentEditPart != null) {
IEditPartIdentifier identifier = DefaultEditPartAdapterFactory
.loadFigureIdentifier(currentEditPart);
if (currentEditPart instanceof ConnectionEditPart) {
while (currentEditPart instanceof ConnectionEditPart) {
ConnectionEditPart connection =
(ConnectionEditPart) currentEditPart;
IEditPartIdentifier connectionIdentifier =
DefaultEditPartAdapterFactory
.loadFigureIdentifier(connection);
idStringList.add(connectionIdentifier.getIdentifier());
Entry<String, ConnectionAnchor> anchorAndName =
FigureCanvasUtil
.getConnectionAnchor(connection);
if (anchorAndName != null) {
idStringList.add(anchorAndName.getKey());
}
EditPart source = connection.getSource();
IEditPartIdentifier sourceIdentifier =
DefaultEditPartAdapterFactory
.loadFigureIdentifier(source);
idStringList.add(sourceIdentifier.getIdentifier());
currentEditPart = source;
}
} else if (identifier != null) {
idStringList.add(identifier.getIdentifier());
}
currentEditPart = currentEditPart.getParent();
}
return idStringList;
}
/**
*
* @param editPart The edit part for which to find the path.
* @param root The root for <code>editPart</code>. This is used to
* avoid adding the root identifier to the returned list.
* @return a list containing the identifier of each edit part between
* <code>editPart</code> and its root.
*/
private List<String> getToolPathToRoot(RootEditPart root,
EditPart editPart) {
List<String> idStringList = new ArrayList<String>();
EditPart currentEditPart = editPart;
if (currentEditPart != null) {
Object model = currentEditPart.getModel();
while (model instanceof PaletteEntry
&& currentEditPart != root.getContents()) {
idStringList.add(((PaletteEntry)model).getLabel());
currentEditPart =
currentEditPart.getParent();
model = currentEditPart.getModel();
}
}
return idStringList;
}
/**
* Sends the given ID information to the client.
*
* @param compId The component identifier to send. May be
* <code>null</code>.
*/
private void sendIdInfo(IComponentIdentifier compId) {
InspectorComponentSelectedMessage message =
new InspectorComponentSelectedMessage();
message.setComponentIdentifier(compId);
try {
AUTServer.getInstance().getCommunicator().send(message);
} catch (CommunicationException e) {
LOG.error("Error occurred while trying to send message to Client.", e); //$NON-NLS-1$
}
}
/**
*
* @param idStringList
* The path to root for a specific edit part or connection
* anchor.
* @param properties
* the properties
* @return a component identifier for the given path, or
* <code>null</code> if no valid component identifier can be
* generated.
*/
private IComponentIdentifier createCompId(List<String> idStringList,
Map<String, String> properties) {
IComponentIdentifier compId = null;
if (!idStringList.isEmpty()) {
Collections.reverse(idStringList);
compId = new ComponentIdentifier();
compId.setHierarchyNames(idStringList);
compId.setComponentPropertiesMap(properties);
}
return compId;
}
}
/** the listener responsible for appending highlight listeners */
private FigureHighlightAppender m_highlightAppender;
/** the listener responsible for handling mouse clicks */
private GefInspectorComponentSelectedListener m_componentSelectedListener;
/**
* Constructor
*/
public GefInspectorListenerAppender() {
m_highlightAppender = new FigureHighlightAppender();
m_componentSelectedListener =
new GefInspectorComponentSelectedListener(m_highlightAppender);
}
/**
* {@inheritDoc}
*/
public void addAutListener() {
final Display display = PlatformUI.getWorkbench().getDisplay();
display.syncExec(new Runnable() {
public void run() {
display.addFilter(SWT.MouseEnter, m_highlightAppender);
display.addFilter(SWT.MouseDown, m_componentSelectedListener);
}
});
}
}