OSDN Git Service

ADT GLE2: drag from Outline view.
authorRaphael Moll <ralf@android.com>
Sun, 18 Jul 2010 02:41:55 +0000 (22:41 -0400)
committerRaphael Moll <ralf@android.com>
Sun, 18 Jul 2010 02:41:55 +0000 (22:41 -0400)
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

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ICanvasTransform.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java

index a1c1c5c..e245558 100644 (file)
@@ -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;
index b42a11d..ea3171f 100755 (executable)
@@ -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;
     }
index 57069f5..03d86fb 100755 (executable)
 
 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.
index dc0b253..8d2279d 100755 (executable)
@@ -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 {
      * <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 ||
@@ -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.
+     * <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 {
 
         /**
index c92b53b..88c891c 100755 (executable)
@@ -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.
  * <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)
@@ -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.
-     * <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);
+                        }
                     }
                 }
             }
-        }
+        });
     }
 
     /**
@@ -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.
+         * <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;
+        }
+    }
+
 }