*/
class LayoutCanvas extends Canvas implements ISelectionProvider {
- public static final String PREFIX_CANVAS_ACTION = "canvas_action_";
+ /* package */ static final String PREFIX_CANVAS_ACTION = "canvas_action_";
/** The layout editor that uses this layout canvas. */
private final LayoutEditor mLayoutEditor;
private ScaleInfo mHScale;
/** Drag source associated with this canvas. */
- private DragSource mSource;
+ private DragSource mDragSource;
/** List of clients listening to selection changes. */
private final ListenerList mSelectionListeners = new ListenerList();
private MenuManager mMenuManager;
+ private CanvasDragSourceListener mDragSourceListener;
+
public LayoutCanvas(LayoutEditor layoutEditor,
RulesEngine rulesEngine,
mDropListener = new CanvasDropListener(this);
mDropTarget.addDropListener(mDropListener);
- mSource = new DragSource(this, DND.DROP_COPY | DND.DROP_MOVE);
- mSource.setTransfer(new Transfer[] {
- TextTransfer.getInstance(),
- SimpleXmlTransfer.getInstance()
- } );
- mSource.addDragListener(new CanvasDragSourceListener());
+ mDragSourceListener = new CanvasDragSourceListener();
+ mDragSource = createDragSource(this, mDragSourceListener);
// --- setup context menu ---
setupGlobalActionHandlers();
mRulesEngine = null;
}
+ if (mDragSource != null) {
+ mDragSource.dispose();
+ mDragSource = null;
+ }
+
if (mClipboard != null) {
mClipboard.dispose();
mClipboard = null;
* Returns the factory to use to convert from {@link CanvasViewInfo} or from
* {@link UiViewElementNode} to {@link INode} proxies.
*/
- public NodeFactory getNodeFactory() {
+ /* package */ NodeFactory getNodeFactory() {
return mNodeFactory;
}
- /** Returns the shared SWT keyboard. */
- public Clipboard getClipboard() {
- return mClipboard;
+ /**
+ * Returns our {@link DragSourceListener}.
+ * This is used by {@link OutlinePage2} to delegate drag source events.
+ */
+ /* package */ DragSourceListener getDragSourceListener() {
+ return mDragSourceListener;
}
/**
*
* @param result The new rendering result, either valid or not.
*/
- public void setResult(ILayoutResult result) {
+ /* package */ void setResult(ILayoutResult result) {
// disable any hover
mHoverRect = null;
redraw();
}
- public void setShowOutline(boolean newState) {
+ /* package */ void setShowOutline(boolean newState) {
mShowOutline = newState;
redraw();
}
- public double getScale() {
+ /* package */ double getScale() {
return mHScale.getScale();
}
- public void setScale(double scale) {
+ /* package */ void setScale(double scale) {
mHScale.setScale(scale);
mVScale.setScale(scale);
redraw();
* @param displayY Y in SWT display coordinates
* @return A new {@link Point} in canvas coordinates
*/
- public Point displayToCanvasPoint(int displayX, int displayY) {
+ /* package */ Point displayToCanvasPoint(int displayX, int displayY) {
// convert screen coordinates to local SWT control coordinates
org.eclipse.swt.graphics.Point p = this.toControl(displayX, displayY);
return new Point(x, y);
}
+ /**
+ * Transforms a point, expressed in canvas coordinates, into "client" coordinates
+ * relative to the control (and not relative to the display.)
+ *
+ * @param canvasX X in the canvas coordinates
+ * @param canvasY Y in the canvas coordinates
+ * @return A new {@link Point} in control client coordinates (not display coordinates)
+ */
+ /* package */ Point canvasToControlPoint(int canvasX, int canvasY) {
+ int x = mHScale.translate(canvasX);
+ int y = mVScale.translate(canvasY);
+ return new Point(x, y);
+ }
//----
// Implementation of ISelectionProvider
* <p/>
* Returns null if there's no action for the given id.
*/
- public IAction getAction(String actionId) {
+ /* package */ IAction getAction(String actionId) {
String prefix = PREFIX_CANVAS_ACTION;
if (mMenuManager == null ||
actionId == null ||
//---
+ /**
+ * Helper class to convert between control pixel coordinates and canvas coordinates.
+ * Takes care of the zooming and offset of the canvas.
+ */
private class ScaleInfo implements ICanvasTransform {
/** Canvas image size (original, before zoom), in pixels */
private int mImgSize;
//---------------
+ /**
+ * Helper to create our drag source for the given control.
+ * <p/>
+ * This is static with package-access so that {@link OutlinePage2} can also
+ * create an exact copy of the source, with the same attributes.
+ */
+ /* package */ static DragSource createDragSource(
+ Control control,
+ DragSourceListener dragSourceListener) {
+ DragSource source = new DragSource(control, DND.DROP_COPY | DND.DROP_MOVE);
+ source.setTransfer(new Transfer[] {
+ TextTransfer.getInstance(),
+ SimpleXmlTransfer.getInstance()
+ } );
+ source.addDragListener(dragSourceListener);
+ return source;
+ }
+
+
+ /**
+ * Our canvas {@link DragSourceListener}. Handles drag being started and finished
+ * and generating the drag data.
+ */
private class CanvasDragSourceListener implements DragSourceListener {
/**
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.swt.dnd.DragSource;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.INullSelectionListener;
-import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
-import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
/**
* An outline page for the GLE2 canvas view.
* <p/>
- * The page is created by {@link LayoutEditor#getAdapter(Class)}.
+ * The page is created by {@link LayoutEditor#getAdapter(Class)}. This means
+ * we have *one* instance of the outline page per open canvas editor.
+ * <p/>
* It sets itself as a listener on the site's selection service in order to be
* notified of the canvas' selection changes.
* The underlying page is also a selection provider (via IContentOutlinePage)
implements ISelectionListener, INullSelectionListener {
/**
+ * The graphical editor that created this outline.
+ */
+ private final GraphicalEditorPart mGraphicalEditorPart;
+
+ /**
* RootWrapper is a workaround: we can't set the input of the treeview to its root
* element, so we introduce a fake parent.
*/
private final RootWrapper mRootWrapper = new RootWrapper();
- /** Part listener, to update the context menu associated with GraphicalEditorPart. */
- private PartListener mPartListener;
- public OutlinePage2() {
+ /**
+ * Menu manager for the context menu actions.
+ * The actions delegate to the current GraphicalEditorPart.
+ */
+ private MenuManager mMenuManager;
+
+ /**
+ * Drag source, created with the same attributes as the one from {@link LayoutCanvas}.
+ * The drag source listener delegates to the current GraphicalEditorPart.
+ */
+ private DragSource mSource;
+
+ public OutlinePage2(GraphicalEditorPart graphicalEditorPart) {
super();
+ mGraphicalEditorPart = graphicalEditorPart;
}
@Override
}
});
- // Listen to selection changes from the layout editor
- getSite().getPage().addSelectionListener(this);
+
+ mSource = LayoutCanvas.createDragSource(getControl(), new DelegateDragSourceListener());
setupContextMenu();
+
+ // Listen to selection changes from the layout editor
+ getSite().getPage().addSelectionListener(this);
}
@Override
public void dispose() {
- mRootWrapper.setRoot(null);
+ if (mSource != null) {
+ mSource.dispose();
+ mSource = null;
+ }
- IWorkbenchWindow win = getSite().getWorkbenchWindow();
- win.getPartService().removePartListener(mPartListener);
+ mRootWrapper.setRoot(null);
getSite().getPage().removeSelectionListener(this);
super.dispose();
* by the {@link LayoutCanvas}. All the processing is actually handled
* directly by the canvas and this viewer only gets refreshed as a
* consequence of the canvas changing the XML model.
- * <p/>
- * To do that, we create actions that currently listen to the active
- * part and only defer to an active layout canvas.
*/
private void setupContextMenu() {
- MenuManager mm = new MenuManager();
- mm.removeAll();
+ mMenuManager = new MenuManager();
+ mMenuManager.removeAll();
final String prefix = LayoutCanvas.PREFIX_CANVAS_ACTION;
- mm.add(new DelegateAction(prefix + ActionFactory.CUT.getId()));
- mm.add(new DelegateAction(prefix + ActionFactory.COPY.getId()));
- mm.add(new DelegateAction(prefix + ActionFactory.PASTE.getId()));
+ mMenuManager.add(new DelegateAction(prefix + ActionFactory.CUT.getId()));
+ mMenuManager.add(new DelegateAction(prefix + ActionFactory.COPY.getId()));
+ mMenuManager.add(new DelegateAction(prefix + ActionFactory.PASTE.getId()));
- mm.add(new Separator());
+ mMenuManager.add(new Separator());
- mm.add(new DelegateAction(prefix + ActionFactory.DELETE.getId()));
- mm.add(new DelegateAction(prefix + ActionFactory.SELECT_ALL.getId()));
+ mMenuManager.add(new DelegateAction(prefix + ActionFactory.DELETE.getId()));
+ mMenuManager.add(new DelegateAction(prefix + ActionFactory.SELECT_ALL.getId()));
- getControl().setMenu(mm.createContextMenu(getControl()));
-
- mPartListener = new PartListener(mm);
- IWorkbenchWindow win = getSite().getWorkbenchWindow();
- win.getPartService().addPartListener(mPartListener);
- }
-
- /**
- * Listen to part changes.
- * <p/>
- * This listener only cares specifically about GLE2's {@link GraphicalEditorPart} changing.
- * When the part changes, the delegate menu actions are refreshed to make sure that the
- * ouline's context menu always points to the current active layout canvas.
- */
- private static class PartListener implements IPartListener {
- private final MenuManager mMenuManager;
-
- public PartListener(MenuManager menuManager) {
- mMenuManager = menuManager;
- }
-
- public void partOpened(IWorkbenchPart part) {
- // pass
- }
-
- public void partActivated(IWorkbenchPart part) {
- GraphicalEditorPart gep = getGraphicalEditorPart(part);
- if (gep != null) {
- updateMenuActions(gep);
- }
- }
-
- public void partBroughtToTop(IWorkbenchPart part) {
- GraphicalEditorPart gep = getGraphicalEditorPart(part);
- if (gep != null) {
- updateMenuActions(gep);
- }
- }
-
- public void partDeactivated(IWorkbenchPart part) {
- GraphicalEditorPart gep = getGraphicalEditorPart(part);
- if (gep != null) {
- updateMenuActions(gep);
- }
- }
+ getControl().setMenu(mMenuManager.createContextMenu(getControl()));
- public void partClosed(IWorkbenchPart part) {
- GraphicalEditorPart gep = getGraphicalEditorPart(part);
- if (gep != null) {
- updateMenuActions(gep);
- }
- }
-
- /**
- * Returns a non-null reference on the {@link GraphicalEditorPart} if this
- * is the part that changed, or null.
- */
- private GraphicalEditorPart getGraphicalEditorPart(IWorkbenchPart part) {
- if (part instanceof LayoutEditor) {
- part = ((LayoutEditor) part).getActiveEditor();
- }
- if (part instanceof GraphicalEditorPart) {
- return (GraphicalEditorPart) part;
- }
- return null;
- }
-
- /**
- * For every action contributed to our menu manager, ask the action to
- * update itself. The action will refresh its target action to match the
- * one from the given {@link GraphicalEditorPart}. If the editor part is
- * null, the delegate action will unlink from its target.
- */
- private void updateMenuActions(GraphicalEditorPart editorPart) {
- for (IContributionItem contrib : mMenuManager.getItems()) {
- if (contrib instanceof ActionContributionItem) {
- IAction action = ((ActionContributionItem) contrib).getAction();
- if (action instanceof DelegateAction) {
- ((DelegateAction) action).updateFromEditorPart(editorPart);
+ mMenuManager.addMenuListener(new IMenuListener() {
+ public void menuAboutToShow(IMenuManager manager) {
+ // Update all actions to match their LayoutCanvas counterparts
+ for (IContributionItem contrib : mMenuManager.getItems()) {
+ if (contrib instanceof ActionContributionItem) {
+ IAction action = ((ActionContributionItem) contrib).getAction();
+ if (action instanceof DelegateAction) {
+ ((DelegateAction) action).updateFromEditorPart(mGraphicalEditorPart);
+ }
}
}
}
- }
+ });
}
/**
/** Updates this action to delegate to its counterpart in the given editor part */
public void updateFromEditorPart(GraphicalEditorPart editorPart) {
- if (editorPart == null) {
+ LayoutCanvas canvas = editorPart == null ? null : editorPart.getCanvasControl();
+ if (canvas == null) {
mTargetAction = null;
} else {
- mTargetAction = editorPart.getCanvasAction(mCanvasActionId);
+ mTargetAction = canvas.getAction(mCanvasActionId);
}
if (mTargetAction != null) {
}
}
}
+
+
+ // --- Drag Source ---
+
+ private class DelegateDragSourceListener implements DragSourceListener {
+
+ public void dragStart(DragSourceEvent event) {
+ if (!adjustEventCoordinates(event)) {
+ event.doit = false;
+ return;
+ }
+ LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
+ if (canvas != null) {
+ canvas.getDragSourceListener().dragStart(event);
+ }
+ }
+
+ public void dragSetData(DragSourceEvent event) {
+ LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
+ if (canvas != null) {
+ canvas.getDragSourceListener().dragSetData(event);
+ }
+ }
+
+ public void dragFinished(DragSourceEvent event) {
+ LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
+ if (canvas != null) {
+ canvas.getDragSourceListener().dragFinished(event);
+ }
+ }
+
+ /**
+ * Finds the element under which the drag started and adjusts
+ * its event coordinates to match the canvas *control* coordinates.
+ * <p/>
+ * Returns false if no element was found at the given position,
+ * which will cancel the drag start.
+ */
+ private boolean adjustEventCoordinates(DragSourceEvent event) {
+ int x = event.x;
+ int y = event.y;
+ ViewerCell cell = getTreeViewer().getCell(new Point(x, y));
+ if (cell != null) {
+ Rectangle cr = cell.getBounds();
+ Object item = cell.getElement();
+
+ if (cr != null && !cr.isEmpty() && item instanceof CanvasViewInfo) {
+ CanvasViewInfo vi = (CanvasViewInfo) item;
+ Rectangle vir = vi.getAbsRect();
+
+ // interpolate from the "cr" bounding box to the "vir" bounding box
+ double ratio = (double) vir.width / (double) cr.width;
+ x = (int) (vir.x + ratio * (x - cr.x));
+ ratio = (double) vir.height / (double) cr.height;
+ y = (int) (vir.y + ratio * (y - cr.y));
+
+ LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl();
+ if (canvas != null) {
+ com.android.ide.eclipse.adt.editors.layout.gscripts.Point p =
+ canvas.canvasToControlPoint(x, y);
+
+ event.x = p.x;
+ event.y = p.y;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+
}