OSDN Git Service

ADT GLE2: Support zoom and scrollbar in canvas.
authorRaphael <raphael@google.com>
Wed, 24 Feb 2010 22:16:07 +0000 (14:16 -0800)
committerRaphael <raphael@google.com>
Wed, 24 Feb 2010 22:45:35 +0000 (14:45 -0800)
Change-Id: I98442be1d01cff0227244c4e1d346b8642106a79

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.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/LayoutCanvas.java

index 10c7d8e..8faf326 100755 (executable)
@@ -21,6 +21,7 @@ import com.android.ide.eclipse.adt.editors.layout.gscripts.IGraphics;
 import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
 import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
 import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas.ScaleTransform;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.SWTException;
@@ -66,14 +67,14 @@ public class GCWrapper implements IGraphics {
     /** The cached pixel height of the default current font. */
     private int mFontHeight = 0;
 
-    /** The offset of the canvas in X. */
-    private final int mX;
-    /** The offset of the canvas in Y. */
-    private final int mY;
+    /** The scaling of the canvas in X. */
+    private final ScaleTransform mHScale;
+    /** The scaling of the canvas in Y. */
+    private final ScaleTransform mVScale;
 
-    public GCWrapper(int offsetX, int offsetY) {
-        mX = offsetX;
-        mY = offsetY;
+    public GCWrapper(ScaleTransform hScale, ScaleTransform vScale) {
+        mHScale = hScale;
+        mVScale = vScale;
         mGc = null;
     }
 
@@ -181,7 +182,11 @@ public class GCWrapper implements IGraphics {
 
     public void drawLine(int x1, int y1, int x2, int y2) {
         checkGC();
-        getGc().drawLine(x1 + mX, y1 + mY, x2 + mX, y2 + mY);
+        x1 = mHScale.translate(x1);
+        y1 = mVScale.translate(y1);
+        x2 = mHScale.translate(x2);
+        y2 = mVScale.translate(y2);
+        getGc().drawLine(x1, y1, x2, y2);
     }
 
     public void drawLine(Point p1, Point p2) {
@@ -190,7 +195,11 @@ public class GCWrapper implements IGraphics {
 
     public void drawRect(int x1, int y1, int x2, int y2) {
         checkGC();
-        getGc().drawRectangle(x1 + mX, y1 + mY, x2-x1, y2-y1);
+        int x = mHScale.translate(x1);
+        int y = mVScale.translate(y1);
+        int w = mHScale.scale(x2 - x1);
+        int h = mVScale.scale(y2 - y1);
+        getGc().drawRectangle(x, y, w, h);
     }
 
     public void drawRect(Point p1, Point p2) {
@@ -199,12 +208,20 @@ public class GCWrapper implements IGraphics {
 
     public void drawRect(Rect r) {
         checkGC();
-        getGc().drawRectangle(r.x + mX, r.y + mY, r.w, r.h);
+        int x = mHScale.translate(r.x);
+        int y = mVScale.translate(r.y);
+        int w = mHScale.scale(r.w);
+        int h = mVScale.scale(r.h);
+        getGc().drawRectangle(x, y, w, h);
     }
 
     public void fillRect(int x1, int y1, int x2, int y2) {
         checkGC();
-        getGc().fillRectangle(x1 + mX, y1 + mY, x2-x1, y2-y1);
+        int x = mHScale.translate(x1);
+        int y = mVScale.translate(y1);
+        int w = mHScale.scale(x2 - x1);
+        int h = mVScale.scale(y2 - y1);
+        getGc().fillRectangle(x, y, w, h);
     }
 
     public void fillRect(Point p1, Point p2) {
@@ -213,12 +230,18 @@ public class GCWrapper implements IGraphics {
 
     public void fillRect(Rect r) {
         checkGC();
-        getGc().fillRectangle(r.x + mX, r.y + mY, r.w, r.h);
+        int x = mHScale.translate(r.x);
+        int y = mVScale.translate(r.y);
+        int w = mHScale.scale(r.w);
+        int h = mVScale.scale(r.h);
+        getGc().fillRectangle(x, y, w, h);
     }
 
     public void drawString(String string, int x, int y) {
         checkGC();
-        getGc().drawString(string, x + mX, y + mY, true /*isTransparent*/);
+        x = mHScale.translate(x);
+        y = mVScale.translate(y);
+        getGc().drawString(string, x, y, true /*isTransparent*/);
     }
 
     public void drawString(String string, Point topLeft) {
index 9d2ae69..900fbc4 100755 (executable)
@@ -201,6 +201,26 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
 
         CustomToggle[] toggles = new CustomToggle[] {
                 new CustomToggle(
+                        "-",
+                        null, //image
+                        "Canvas zoom out."
+                        ) {
+                    @Override
+                    public void onSelected(boolean newState) {
+                        rescale(-1);
+                    }
+                },
+                new CustomToggle(
+                        "+",
+                        null, //image
+                        "Canvas zoom in."
+                        ) {
+                    @Override
+                    public void onSelected(boolean newState) {
+                        rescale(+1);
+                    }
+                },
+                new CustomToggle(
                         "Explode",
                         null, //image
                         "Displays extra margins in the layout."
@@ -251,6 +271,23 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
         reloadPalette();
     }
 
+    /**
+     * Rescales canvas.
+     * @param direction +1 for zoom in, -1 for zoom out
+     */
+    private void rescale(int direction) {
+        double s = mLayoutCanvas.getScale();
+
+        if (direction > 0) {
+            s = s * 2;
+        } else {
+            s = s / 2;
+        }
+
+        mLayoutCanvas.setScale(s);
+
+    }
+
     private void setupEditActions() {
 
         IActionBars actionBars = getEditorSite().getActionBars();
index c27e981..8b75ecd 100755 (executable)
@@ -28,11 +28,15 @@ import org.eclipse.swt.dnd.Clipboard;
 import org.eclipse.swt.dnd.DND;
 import org.eclipse.swt.dnd.DropTarget;
 import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseListener;
 import org.eclipse.swt.events.MouseMoveListener;
 import org.eclipse.swt.events.PaintEvent;
 import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.GC;
@@ -43,6 +47,7 @@ import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.ScrollBar;
 
 import java.awt.image.BufferedImage;
 import java.awt.image.DataBufferInt;
@@ -115,7 +120,7 @@ import java.util.ListIterator;
 
     /** GC wrapper given to the IViewRule methods. The GC itself is only defined in the
      *  context of {@link #onPaint(PaintEvent)}; otherwise it is null. */
-    private GCWrapper mGCWrapper;
+    private final GCWrapper mGCWrapper;
 
     /** Default font used on the canvas. Do not dispose, it's a system font. */
     private Font mFont;
@@ -123,7 +128,10 @@ import java.util.ListIterator;
     /** Current hover view info. Null when no mouse hover. */
     private CanvasViewInfo mHoverViewInfo;
 
-    /** Current mouse hover border rectangle. Null when there's no mouse hover. */
+    /** Current mouse hover border rectangle. Null when there's no mouse hover.
+     * The rectangle coordinates do not take account of the translation, which must
+     * be applied to the rectangle when drawing.
+     */
     private Rectangle mHoverRect;
 
     /** Hover border color. Must be disposed, it's NOT a system color. */
@@ -150,12 +158,20 @@ import java.util.ListIterator;
     /** Factory that can create {@link INode} proxies. */
     private final NodeFactory mNodeFactory = new NodeFactory();
 
+    /** Vertical scaling & scrollbar information. */
+    private ScaleInfo mVScale;
+
+    /** Horizontal scaling & scrollbar information. */
+    private ScaleInfo mHScale;
 
     public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) {
         super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL);
         mRulesEngine = rulesEngine;
 
-        mGCWrapper = new GCWrapper(IMAGE_MARGIN, IMAGE_MARGIN);
+        mHScale = new ScaleInfo(getHorizontalBar());
+        mVScale = new ScaleInfo(getVerticalBar());
+
+        mGCWrapper = new GCWrapper(mHScale, mVScale);
 
         Display d = getDisplay();
         mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
@@ -170,6 +186,15 @@ import java.util.ListIterator;
             }
         });
 
+        addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                super.controlResized(e);
+                mHScale.setClientSize(getClientArea().width);
+                mVScale.setClientSize(getClientArea().height);
+            }
+        });
+
         addMouseMoveListener(new MouseMoveListener() {
             public void mouseMove(MouseEvent e) {
                 onMouseMove(e);
@@ -290,6 +315,9 @@ import java.util.ListIterator;
 
             // remove the current alternate selection views
             mAltSelection = null;
+
+            mHScale.setSize(mImage.getImageData().width, getClientArea().width);
+            mVScale.setSize(mImage.getImageData().height, getClientArea().height);
         }
 
         redraw();
@@ -300,6 +328,16 @@ import java.util.ListIterator;
         redraw();
     }
 
+    public double getScale() {
+        return mHScale.getScale();
+    }
+
+    public void setScale(double scale) {
+        mHScale.setScale(scale);
+        mVScale.setScale(scale);
+        redraw();
+    }
+
     /**
      * Called by the {@link GraphicalEditorPart} when the Copy action is requested.
      *
@@ -353,6 +391,143 @@ import java.util.ListIterator;
 
     //---
 
+    public interface ScaleTransform {
+        /**
+         * Computes the transformation from a X/Y canvas image coordinate
+         * to client pixel coordinate.
+         * <p/>
+         * This takes into account the {@link LayoutCanvas#IMAGE_MARGIN},
+         * the current scaling and the current translation.
+         *
+         * @param canvasX A canvas image coordinate (X or Y).
+         * @return The transformed coordinate in client pixel space.
+         */
+        public int translate(int canvasX);
+
+        /**
+         * Computes the transformation from a canvas image size (width or height) to
+         * client pixel coordinates.
+         *
+         * @param canwasW A canvas image size (W or H).
+         * @return The transformed coordinate in client pixel space.
+         */
+        public int scale(int canwasW);
+
+        /**
+         * Computes the transformation from a X/Y client pixel coordinate
+         * to canvas image coordinate.
+         * <p/>
+         * This takes into account the {@link LayoutCanvas#IMAGE_MARGIN},
+         * the current scaling and the current translation.
+         * <p/>
+         * This is the inverse of {@link #translate(int)}.
+         *
+         * @param screenX A client pixel space coordinate (X or Y).
+         * @return The transformed coordinate in canvas image space.
+         */
+        public int inverseTranslate(int screenX);
+    }
+
+    private class ScaleInfo implements ScaleTransform {
+        /** Canvas image size (original, before zoom), in pixels */
+        private int mImgSize;
+
+        /** Client size, in pixels */
+        private int mClientSize;
+
+        /** Left-top offset in client pixel coordinates */
+        private int mTranslate;
+
+        /** Scaling factor, > 0 */
+        private double mScale;
+
+        /** Scrollbar widget */
+        ScrollBar mScrollbar;
+
+        public ScaleInfo(ScrollBar scrollbar) {
+            mScrollbar = scrollbar;
+            mScale = 1.0;
+            mTranslate = 0;
+
+            mScrollbar.addSelectionListener(new SelectionAdapter() {
+                @Override
+                public void widgetSelected(SelectionEvent e) {
+                    // User requested scrolling. Changes translation and redraw canvas.
+                    mTranslate = mScrollbar.getSelection();
+                    redraw();
+                }
+            });
+        }
+
+        /**
+         * Sets the new scaling factor. Recomputes scrollbars.
+         * @param scale Scaling factor, > 0.
+         */
+        public void setScale(double scale) {
+            if (mScale != scale) {
+                mScale = scale;
+                resizeScrollbar();
+            }
+        }
+
+        /** Returns current scaling factor. */
+        public double getScale() {
+            return mScale;
+        }
+
+        /** Returns Canvas image size (original, before zoom), in pixels. */
+        public int getImgSize() {
+            return mImgSize;
+        }
+
+        /** Returns the scaled image size in pixels. */
+        public int getScalledImgSize() {
+            return (int) (mImgSize * mScale);
+        }
+
+        /** Changes the size of the canvas image and the client size. Recomputes scrollbars. */
+        public void setSize(int imgSize, int clientSize) {
+            mImgSize = imgSize;
+            setClientSize(clientSize);
+        }
+
+        /** Changes the size of the client size. Recomputes scrollbars. */
+        public void setClientSize(int clientSize) {
+            mClientSize = clientSize;
+            resizeScrollbar();
+        }
+
+        private void resizeScrollbar() {
+            // scaled image size
+            int sx = (int) (mImgSize * mScale);
+
+            // actual client area is always reduced by the margins
+            int cx = mClientSize - 2 * IMAGE_MARGIN;
+
+            if (sx < cx) {
+                mScrollbar.setEnabled(false);
+            } else {
+                mScrollbar.setEnabled(true);
+
+                // max scroll value is the scaled image size
+                // thumb value is the actual viewable area out of the scaled img size
+                mScrollbar.setMaximum(sx);
+                mScrollbar.setThumb(cx);
+            }
+        }
+
+        public int translate(int canvasX) {
+            return IMAGE_MARGIN - mTranslate + (int)(mScale * canvasX);
+        }
+
+        public int scale(int canwasW) {
+            return (int)(mScale * canwasW);
+        }
+
+        public int inverseTranslate(int screenX) {
+            return (int) ((screenX - IMAGE_MARGIN + mTranslate) / mScale);
+        }
+    }
 
     /**
      * Creates or updates the node proxy for this canvas view info.
@@ -420,6 +595,28 @@ import java.util.ListIterator;
     }
 
     /**
+     * Sets the non-text antialias flag for the given GC.
+     * <p/>
+     * Antialias may not work on all platforms and may fail with an exception.
+     *
+     * @param gc the GC to change
+     * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}.
+     * @return The previous aliasing mode if the operation worked,
+     *         or -2 if it failed with an exception.
+     *
+     * @see GC#setAntialias(int)
+     */
+    private int gc_setAntialias(GC gc, int alias) {
+        try {
+            int old = gc.getAntialias();
+            gc.setAntialias(alias);
+            return old;
+        } catch (SWTException e) {
+            return -2;
+        }
+    }
+
+    /**
      * Paints the canvas in response to paint events.
      */
     private void onPaint(PaintEvent e) {
@@ -433,7 +630,29 @@ import java.util.ListIterator;
                     gc_setAlpha(gc, 128);  // half-transparent
                 }
 
-                gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);
+                ScaleInfo hi = mHScale;
+                ScaleInfo vi = mVScale;
+
+                // we only anti-alias when reducing the image size.
+                int oldAlias = -2;
+                if (hi.getScale() < 1.0) {
+                    oldAlias = gc_setAntialias(gc, SWT.ON);
+                }
+
+                gc.drawImage(mImage,
+                        0,                          // srcX
+                        0,                          // srcY
+                        hi.getImgSize(),            // srcWidth
+                        vi.getImgSize(),            // srcHeight
+                        hi.translate(0),            // destX
+                        vi.translate(0),            // destY
+                        hi.getScalledImgSize(),     // destWidth
+                        vi.getScalledImgSize()      // destHeight
+                        );
+
+                if (oldAlias != -2) {
+                    gc_setAntialias(gc, oldAlias);
+                }
 
                 if (!mIsResultValid) {
                     gc_setAlpha(gc, 255);  // opaque
@@ -449,7 +668,13 @@ import java.util.ListIterator;
             if (mHoverRect != null) {
                 gc.setForeground(mHoverFgColor);
                 gc.setLineStyle(SWT.LINE_DOT);
-                gc.drawRectangle(mHoverRect);
+
+                int x = mHScale.translate(mHoverRect.x);
+                int y = mVScale.translate(mHoverRect.y);
+                int w = mHScale.scale(mHoverRect.width);
+                int h = mVScale.scale(mHoverRect.height);
+
+                gc.drawRectangle(x, y, w, h);
             }
 
             int n = mSelections.size();
@@ -479,7 +704,13 @@ import java.util.ListIterator;
     private void drawOutline(GC gc, CanvasViewInfo info) {
 
         Rectangle r = info.getAbsRect();
-        gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
+
+        int x = mHScale.translate(r.x);
+        int y = mVScale.translate(r.y);
+        int w = mHScale.scale(r.width);
+        int h = mVScale.scale(r.height);
+
+        gc.drawRectangle(x, y, w, h);
 
         for (CanvasViewInfo vi : info.getChildren()) {
             drawOutline(gc, vi);
@@ -492,7 +723,11 @@ import java.util.ListIterator;
     private void onMouseMove(MouseEvent e) {
         if (mLastValidResult != null) {
             CanvasViewInfo root = mLastValidViewInfoRoot;
-            CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN);
+
+            int x = mHScale.inverseTranslate(e.x);
+            int y = mVScale.inverseTranslate(e.y);
+
+            CanvasViewInfo vi = findViewInfoAt(x, y);
 
             // We don't hover on the root since it's not a widget per see and it is always there.
             if (vi == root) {
@@ -506,8 +741,7 @@ import java.util.ListIterator;
                 mHoverRect = null;
             } else {
                 Rectangle r = vi.getSelectionRect();
-                mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,
-                                           r.width, r.height);
+                mHoverRect = new Rectangle(r.x, r.y, r.width, r.height);
             }
 
             if (needsUpdate) {
@@ -533,8 +767,9 @@ import java.util.ListIterator;
             boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
             boolean isAlt   = (e.stateMask & SWT.ALT)   != 0;
 
-            int x = e.x - IMAGE_MARGIN;
-            int y = e.y - IMAGE_MARGIN;
+            int x = mHScale.inverseTranslate(e.x);
+            int y = mVScale.inverseTranslate(e.y);
+
             CanvasViewInfo vi = findViewInfoAt(x, y);
 
             if (isShift && !isAlt) {