OSDN Git Service

Add resize feedback tooltip
authorTor Norbye <tnorbye@google.com>
Mon, 11 Jul 2011 05:22:08 +0000 (22:22 -0700)
committerTor Norbye <tnorbye@google.com>
Mon, 11 Jul 2011 22:11:28 +0000 (15:11 -0700)
This changeset adds a "tooltip" during resize operations close to the
mouse cursor which displays feedback about the current resize
dimensions.

The feedback window is similar in appearance to a tooltip, but unlike
SWT tooltips it moves with the cursor and updates the text
dynamically, and appears immediately rather than being tied to mouse
motion timeouts. It is also partially translucent and uses a slightly
smaller font.

Change-Id: I6b663510f078f325d2f7b168c887aeff14de31b8

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DropFeedback.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Gesture.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ResizeGesture.java

index 855d8b0..551cb3c 100755 (executable)
@@ -142,6 +142,12 @@ public class DropFeedback {
     public String errorMessage;
 
     /**
+     * A message to be displayed in a tooltip to the user, which should be short, but
+     * can be multiple lines (use embedded newlines)
+     */
+    public String tooltip;
+
+    /**
      * A mask of the currently held keyboard modifier keys - some combination of
      * {@link #MODIFIER1}, {@link #MODIFIER2}, {@link #MODIFIER3}, or none.
      */
index de03a19..9a789c6 100644 (file)
@@ -235,7 +235,11 @@ public class AbsoluteLayoutRule extends BaseLayoutRule {
     protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent,
             Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
         Rect parentBounds = parent.getBounds();
-        return String.format("Set bounds to (x = %d, y = %d, width = %s, height = %s)",
+        if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) {
+            return super.getResizeUpdateMessage(resizeState, child, parent, newBounds,
+                    horizontalEdge, verticalEdge);
+        }
+        return String.format("x=%d, y=%d\nwidth=%s, height=%s",
                 mRulesEngine.pxToDp(newBounds.x - parentBounds.x),
                 mRulesEngine.pxToDp(newBounds.y - parentBounds.y),
                 resizeState.getWidthAttribute(), resizeState.getHeightAttribute());
index fa87fa4..95a57e8 100644 (file)
@@ -33,21 +33,21 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
 import com.android.ide.common.api.DrawingStyle;
 import com.android.ide.common.api.DropFeedback;
 import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
 import com.android.ide.common.api.IClientRulesEngine;
 import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.IDragElement.IDragAttribute;
 import com.android.ide.common.api.IFeedbackPainter;
 import com.android.ide.common.api.IGraphics;
 import com.android.ide.common.api.IMenuCallback;
 import com.android.ide.common.api.INode;
 import com.android.ide.common.api.INodeHandler;
 import com.android.ide.common.api.MenuAction;
+import com.android.ide.common.api.MenuAction.ChoiceProvider;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
 import com.android.ide.common.api.Segment;
 import com.android.ide.common.api.SegmentType;
-import com.android.ide.common.api.IAttributeInfo.Format;
-import com.android.ide.common.api.IDragElement.IDragAttribute;
-import com.android.ide.common.api.MenuAction.ChoiceProvider;
 import com.android.ide.common.layout.relative.MarginType;
 import com.android.sdklib.SdkConstants;
 import com.android.util.Pair;
@@ -756,7 +756,7 @@ public class BaseLayoutRule extends BaseViewRule {
             }
         }
 
-        feedback.message = getResizeUpdateMessage(state, child, parent,
+        feedback.tooltip = getResizeUpdateMessage(state, child, parent,
                 newBounds, state.horizontalEdgeType, state.verticalEdgeType);
     }
 
@@ -791,8 +791,14 @@ public class BaseLayoutRule extends BaseViewRule {
         String width = resizeState.getWidthAttribute();
         String height = resizeState.getHeightAttribute();
 
-        // U+00D7: Unicode for multiplication sign
-        return String.format("Resize to %s \u00D7 %s", width, height);
+        if (horizontalEdge == null) {
+            return width;
+        } else if (verticalEdge == null) {
+            return height;
+        } else {
+            // U+00D7: Unicode for multiplication sign
+            return String.format("%s \u00D7 %s", width, height);
+        }
     }
 
     /**
index 2d08e62..6222a5a 100644 (file)
@@ -1007,7 +1007,7 @@ public class LinearLayoutRule extends BaseLayoutRule {
 
         if (resizeState.useWeight) {
             String weight = formatFloatAttribute(resizeState.mWeight);
-            String dimension = String.format("layout weight %1$s", weight);
+            String dimension = String.format("weight %1$s", weight);
 
             String width;
             String height;
@@ -1019,8 +1019,14 @@ public class LinearLayoutRule extends BaseLayoutRule {
                 height = resizeState.getHeightAttribute();
             }
 
-            // U+00D7: Unicode for multiplication sign
-            return String.format("Resize to %s \u00D7 %s", width, height);
+            if (horizontalEdge == null) {
+                return width;
+            } else if (verticalEdge == null) {
+                return height;
+            } else {
+                // U+00D7: Unicode for multiplication sign
+                return String.format("%s \u00D7 %s", width, height);
+            }
         } else {
             return super.getResizeUpdateMessage(state, child, parent, newBounds,
                     horizontalEdge, verticalEdge);
index ba09220..59b9602 100644 (file)
@@ -22,6 +22,7 @@ import org.eclipse.swt.dnd.DropTargetEvent;
 import org.eclipse.swt.events.MenuDetectEvent;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.Point;
 
 /**
  * A {@link ControlPoint} is a coordinate in the canvas control which corresponds
@@ -182,4 +183,13 @@ public final class ControlPoint {
         }
         return true;
     }
+
+    /**
+     * Returns this point as an SWT point in the display coordinate system
+     *
+     * @return this point as an SWT point in the display coordinate system
+     */
+    public Point toDisplayPoint() {
+        return mCanvas.toDisplay(x, y);
+    }
 }
index 16577c1..cab569f 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
 
+import com.android.util.Pair;
+
 import org.eclipse.swt.events.KeyEvent;
 
 import java.util.Collections;
@@ -139,4 +141,16 @@ public abstract class Gesture {
     public boolean keyReleased(KeyEvent event) {
         return false;
     }
+
+    /**
+     * Returns whether tooltips should be display below and to the right of the mouse
+     * cursor.
+     *
+     * @return a pair of booleans, the first indicating whether the tooltip should be
+     *         below and the second indicating whether the tooltip should be displayed to
+     *         the right of the mouse cursor.
+     */
+    public Pair<Boolean, Boolean> getTooltipPosition() {
+        return Pair.of(true, true);
+    }
 }
index 0c84728..8c4bf15 100644 (file)
@@ -70,6 +70,9 @@ public class GestureManager {
     /** A listener for drag source events. */
     private final DragSourceListener mDragSourceListener = new CanvasDragSourceListener();
 
+    /** Tooltip shown during the gesture, or null */
+    private GestureToolTip mTooltip;
+
     /**
      * The list of overlays associated with {@link #mCurrentGesture}. Will be
      * null before it has been initialized lazily by the paint routine (the
@@ -363,7 +366,6 @@ public class GestureManager {
     /**
      * Update the Eclipse status message with any feedback messages from the given
      * {@link DropFeedback} object, or clean up if there is no more feedback to process
-     *
      * @param feedback the feedback whose message we want to display, or null to clear the
      *            message if previously set
      */
@@ -393,6 +395,18 @@ public class GestureManager {
             status.setMessage(null);
             status.setErrorMessage(null);
         }
+
+        // Tooltip
+        if (feedback != null && feedback.tooltip != null) {
+            if (mTooltip == null) {
+                Pair<Boolean,Boolean> position = mCurrentGesture.getTooltipPosition();
+                mTooltip = new GestureToolTip(mCanvas, position.getFirst(), position.getSecond());
+            }
+            mTooltip.update(feedback.tooltip);
+        } else if (mTooltip != null) {
+            mTooltip.dispose();
+            mTooltip = null;
+        }
     }
 
     /**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java
new file mode 100644 (file)
index 0000000..7d71ec7
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A dedicated tooltip used during gestures, for example to show the resize dimensions.
+ * <p>
+ * This is necessary because {@link org.eclipse.jface.window.ToolTip} causes flicker when
+ * used to dynamically update the position and text of the tip, and it does not seem to
+ * have setter methods to update the text or position without recreating the tip.
+ */
+public class GestureToolTip {
+    /**
+     * The alpha to use for the tooltip window (which sadly will apply to the tooltip text
+     * as well.)
+     */
+    private static final int SHELL_TRANSPARENCY = 220;
+
+    /** The size of the font displayed in the tooltip */
+    private static final int FONT_SIZE = 9;
+
+    /** Horizontal delta from the mouse cursor to shift the tooltip by */
+    private static final int OFFSET_X = 20;
+
+    /** Vertical delta from the mouse cursor to shift the tooltip by */
+    private static final int OFFSET_Y = 20;
+
+    /** The label which displays the tooltip */
+    private CLabel mLabel;
+
+    /** The shell holding the tooltip */
+    private Shell mShell;
+
+    /** The font shown in the label; held here such that it can be disposed of after use */
+    private Font mFont;
+
+    /** Should the tooltip be displayed below the cursor? */
+    private boolean mBelow;
+
+    /** Should the tooltip be displayed to the right of the cursor? */
+    private boolean mToRightOf;
+
+    /**
+     * Creates a new tooltip over the given parent with the given relative position.
+     *
+     * @param parent the parent control
+     * @param below if true, display the tooltip below the mouse cursor otherwise above
+     * @param toRightOf if true, display the tooltip to the right of the mouse cursor,
+     *            otherwise to the left
+     */
+    public GestureToolTip(Composite parent, boolean below, boolean toRightOf) {
+        mBelow = below;
+        mToRightOf = toRightOf;
+
+        mShell = new Shell(parent.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_FOCUS);
+        mShell.setLayout(new FillLayout());
+        mShell.setAlpha(SHELL_TRANSPARENCY);
+
+        Display display = parent.getDisplay();
+        mLabel = new CLabel(mShell, SWT.SHADOW_NONE);
+        mLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
+        mLabel.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
+
+        Font systemFont = display.getSystemFont();
+        FontData[] fd = systemFont.getFontData();
+        for (int i = 0; i < fd.length; i++) {
+            fd[i].setHeight(FONT_SIZE);
+        }
+        mFont = new Font(display, fd);
+        mLabel.setFont(mFont);
+
+        mShell.setVisible(false);
+
+    }
+
+    /**
+     * Show the tooltip at the given position and with the given text
+     *
+     * @param text the new text to be displayed
+     */
+    public void update(String text) {
+        Point location = mShell.getDisplay().getCursorLocation();
+
+        mLabel.setText(text);
+
+        // Pack the label to its minimum size -- unless we are positioning the tooltip
+        // on the left. Because of the way SWT works (at least on the OSX) this sometimes
+        // creates flicker, because when we switch to a longer string (such as when
+        // switching from "52dp" to "wrap_content" during a resize) the window size will
+        // change first, and then the location will update later - so there will be a
+        // brief flash of the longer label before it is moved to the right position on the
+        // left. To work around this, we simply pass false to pack such that it will reuse
+        // its cached size, which in practice means that for labels on the right, the
+        // label will grow but not shrink.
+        // This workaround is disabled because it doesn't work well in Eclipse 3.5; the
+        // labels don't grow when they should. Re-enable when we drop 3.5 support.
+        //boolean changed = mToRightOf;
+        boolean changed = true;
+
+        mShell.pack(changed);
+        Point size = mShell.getSize();
+
+        if (mBelow) {
+            location.y += OFFSET_Y;
+        } else {
+            location.y -= OFFSET_Y;
+            location.y -= size.y;
+        }
+
+        if (mToRightOf) {
+            location.x += OFFSET_X;
+        } else {
+            location.x -= OFFSET_X;
+            location.x -= size.x;
+        }
+
+        mShell.setLocation(location);
+
+        if (!mShell.isVisible()) {
+            mShell.setVisible(true);
+        }
+    }
+
+    /** Hide the tooltip and dispose of any associated resources */
+    public void dispose() {
+        mShell.dispose();
+        mFont.dispose();
+
+        mShell = null;
+        mFont = null;
+        mLabel = null;
+    }
+}
index 1714828..0d7f402 100644 (file)
@@ -23,6 +23,7 @@ import com.android.ide.common.api.SegmentType;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.Position;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+import com.android.util.Pair;
 
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.graphics.GC;
@@ -120,6 +121,11 @@ public class ResizeGesture extends Gesture {
         mCanvas.getSelectionOverlay().setHidden(false);
     }
 
+    @Override
+    public Pair<Boolean, Boolean> getTooltipPosition() {
+        return Pair.of(mHorizontalEdge != SegmentType.TOP, mVerticalEdge != SegmentType.LEFT);
+    }
+
     /**
      * For the new mouse position, compute the resized bounds (the bounding rectangle that
      * the view should be resized to). This is not just a width or height, since in some