From a528d678b9a8b2d38030713281314c800e29a9ef Mon Sep 17 00:00:00 2001 From: Raphael Moll Date: Sat, 17 Jul 2010 22:41:55 -0400 Subject: [PATCH] ADT GLE2: drag from Outline view. The drag source listener delegates the handling to the canvas. Changed the OutlinePage2 to no longer listen to parts activations. Instead there's one instance of OutlinePage2 per instance of the GraphicalEditorPart and the link is provided in the constructor directly. Change-Id: I8bee65b2a7f75bd1436082c9a9753c561d8a6cab --- .../adt/internal/editors/layout/LayoutEditor.java | 2 +- .../editors/layout/gle2/GraphicalEditorPart.java | 5 +- .../editors/layout/gle2/ICanvasTransform.java | 7 +- .../internal/editors/layout/gle2/LayoutCanvas.java | 82 +++++-- .../internal/editors/layout/gle2/OutlinePage2.java | 237 ++++++++++++--------- 5 files changed, 207 insertions(+), 126 deletions(-) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index a1c1c5c5e..e2455588c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -325,7 +325,7 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput, mOutline = mOutlineForGle1; } else if (mOutline == null && mGraphicalEditor instanceof GraphicalEditorPart) { - mOutline = new OutlinePage2(); + mOutline = new OutlinePage2((GraphicalEditorPart) mGraphicalEditor); } return mOutline; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index b42a11dc8..ea3171f38 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -63,7 +63,6 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.ui.parts.SelectionSynchronizer; -import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; @@ -831,9 +830,9 @@ public class GraphicalEditorPart extends EditorPart return mLayoutEditor; } - public IAction getCanvasAction(String canvasActionId) { + /* package */ LayoutCanvas getCanvasControl() { if (mCanvasViewer != null) { - return mCanvasViewer.getCanvas().getAction(canvasActionId); + return mCanvasViewer.getCanvas(); } return null; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ICanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ICanvasTransform.java index 57069f51a..03d86fb0e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ICanvasTransform.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ICanvasTransform.java @@ -16,7 +16,12 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; -public interface ICanvasTransform { +/** + * Interface for a class that can convert between client pixel's coordinates + * and canvas coordinates. Each instance of such a transform deals with only + * one axis, so clients need to use 2 instances for X and Y. + */ +/* package */ interface ICanvasTransform { /** * Margin around the rendered image. * Should be enough space to display the layout width and height pseudo widgets. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index dc0b25393..8d2279db1 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -129,7 +129,7 @@ import java.util.Set; */ 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; @@ -221,7 +221,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { 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(); @@ -250,6 +250,8 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { private MenuManager mMenuManager; + private CanvasDragSourceListener mDragSourceListener; + public LayoutCanvas(LayoutEditor layoutEditor, RulesEngine rulesEngine, @@ -316,12 +318,8 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { 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(); @@ -359,6 +357,11 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { mRulesEngine = null; } + if (mDragSource != null) { + mDragSource.dispose(); + mDragSource = null; + } + if (mClipboard != null) { mClipboard.dispose(); mClipboard = null; @@ -392,13 +395,16 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { * 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; } /** @@ -411,7 +417,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { * * @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; @@ -459,16 +465,16 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { 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(); @@ -483,7 +489,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { * @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); @@ -492,6 +498,19 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { 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 @@ -622,7 +641,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { *

* 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 || @@ -644,6 +663,10 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { //--- + /** + * 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; @@ -1260,6 +1283,29 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { //--------------- + /** + * Helper to create our drag source for the given control. + *

+ * 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 { /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java index c92b53b2a..88c891c0c 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java @@ -27,6 +27,8 @@ import org.eclipse.jface.action.Action; 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; @@ -39,13 +41,17 @@ import org.eclipse.jface.viewers.TreePath; 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; @@ -68,7 +74,9 @@ import java.util.ArrayList; /** * An outline page for the GLE2 canvas view. *

- * 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. + *

* 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) @@ -81,15 +89,31 @@ public class OutlinePage2 extends ContentOutlinePage 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 @@ -134,18 +158,23 @@ public class OutlinePage2 extends ContentOutlinePage } }); - // 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(); @@ -372,108 +401,37 @@ public class OutlinePage2 extends ContentOutlinePage * 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. - *

- * 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. - *

- * 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); + } } } } - } + }); } /** @@ -521,10 +479,11 @@ public class OutlinePage2 extends ContentOutlinePage /** 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) { @@ -545,4 +504,76 @@ public class OutlinePage2 extends ContentOutlinePage } } } + + + // --- 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. + *

+ * 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; + } + } + } -- 2.11.0