summaryrefslogtreecommitdiffstats
path: root/tools/eclipse
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2009-09-04 17:56:59 -0700
committerRaphael <raphael@google.com>2009-09-04 17:59:56 -0700
commit9135db1149f457d75be0492f510344b03bcf18bc (patch)
tree8b9ffc3f579809b0d89f0ae3f999ad22195786ce /tools/eclipse
parent0eb96d6433325b54678272b485bd2b88df3b74cb (diff)
downloadandroid_development-9135db1149f457d75be0492f510344b03bcf18bc.tar.gz
android_development-9135db1149f457d75be0492f510344b03bcf18bc.tar.bz2
android_development-9135db1149f457d75be0492f510344b03bcf18bc.zip
ADT: Display selection and mouse hover in GLE canvas.
Change-Id: Icc2f8331a099905d6e1aaa52b36cc17a7190cc4b
Diffstat (limited to 'tools/eclipse')
-rwxr-xr-xtools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java36
-rwxr-xr-xtools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java321
2 files changed, 311 insertions, 46 deletions
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
index 3504fe741..cc3d928d1 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
@@ -85,6 +85,7 @@ import java.util.Map;
* @since GLE2
*
* TODO List:
+ * - display error icon
* - finish palette (see palette's todo list)
* - finish canvas (see canva's todo list)
* - completly rethink the property panel
@@ -901,6 +902,9 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
/**
* Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
* the implementation API level.
+ *
+ * Implementation detail: the bridge's computeLayout() method already returns a newly
+ * allocated ILayourResult.
*/
@SuppressWarnings("deprecation")
private static ILayoutResult computeLayout(LayoutBridge bridge,
@@ -954,38 +958,6 @@ public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutE
return mConfigComposite.getScreenBounds();
}
- /** @deprecated for GLE2 */
- private void resetNodeBounds(UiElementNode node) {
- node.setEditData(null);
-
- List<UiElementNode> children = node.getUiChildren();
- for (UiElementNode child : children) {
- resetNodeBounds(child);
- }
- }
-
- /** @deprecated for GLE2 */
- private void updateNodeWithBounds(ILayoutViewInfo r) {
- if (r != null) {
- // update the node itself, as the viewKey is the XML node in this implementation.
- Object viewKey = r.getViewKey();
- if (viewKey instanceof UiElementNode) {
- Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(),
- r.getRight()-r.getLeft(), r.getBottom() - r.getTop());
-
- ((UiElementNode)viewKey).setEditData(bounds);
- }
-
- // and then its children.
- ILayoutViewInfo[] children = r.getChildren();
- if (children != null) {
- for (ILayoutViewInfo child : children) {
- updateNodeWithBounds(child);
- }
- }
- }
- }
-
public void reloadPalette() {
if (mPalette != null) {
mPalette.reloadPalette(mLayoutEditor.getTargetData());
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
index 0ce381d5c..69846eb68 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
@@ -17,15 +17,25 @@
package com.android.ide.eclipse.adt.internal.editors.layout;
import com.android.layoutlib.api.ILayoutResult;
+import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
+import org.eclipse.swt.SWT;
+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.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
+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 java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
@@ -39,36 +49,127 @@ import java.awt.image.Raster;
* @since GLE2
*
* TODO list:
+ * - gray on error, keep select but disable d'n'd.
* - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
- * - handle selection (will need the model, aka the root node)/
- * - handle drop target (from palette)/
- * - handle drag'n'drop (internal, for moving/duplicating)/
- * - handle context menu (depending on selection)/
- * - selection synchronization with the outline (both ways)/
+ * - handle selection (will need the model, aka the root node).
+ * - handle drop target (from palette).
+ * - handle drag'n'drop (internal, for moving/duplicating).
+ * - handle context menu (depending on selection).
+ * - selection synchronization with the outline (both ways).
* - preserve selection during editor input change if applicable (e.g. when changing configuration.)
*/
public class LayoutCanvas extends Canvas {
+ private static final int IMAGE_MARGIN = 5;
+ private static final int SELECTION_MARGIN = 2;
+
+ private ILayoutResult mLastValidResult;
+
+ /** Current background image. Null when there's no image. */
private Image mImage;
+ /** Current selected view info. Null when none is selected. */
+ private ILayoutViewInfo mSelectionViewInfo;
+ /** Current selection border rectangle. Null when there's no selection. */
+ private Rectangle mSelectionRect;
+ /** The name displayed over the selection, typically the widget class name. */
+ private String mSelectionName;
+ /** Selection border color. Do not dispose, it's a system color. */
+ private Color mSelectionFgColor;
+ /** Selection name font. Do not dispose, it's a system font. */
+ private Font mSelectionFont;
+ /** Pixel height of the font displaying the selection name. Initially set to 0 and only
+ * initialized in onPaint() when we have a GC. */
+ private int mSelectionFontHeight;
+
+ /** Current hover view info. Null when no mouse hover. */
+ private ILayoutViewInfo mHoverViewInfo;
+ /** Current mouse hover border rectangle. Null when there's no mouse hover. */
+ private Rectangle mHoverRect;
+ /** Hover border color. Do not dispose, it's a system color. */
+ private Color mHoverFgColor;
+
+ private boolean mIsResultValid;
+
+
+
public LayoutCanvas(Composite parent, int style) {
- super(parent, style);
+ super(parent, style | SWT.DOUBLE_BUFFERED);
+
+ Display d = getDisplay();
+ mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
+ mHoverFgColor = mSelectionFgColor;
+
+ mSelectionFont = d.getSystemFont();
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
- paint(e);
+ onPaint(e);
+ }
+ });
+
+ addMouseMoveListener(new MouseMoveListener() {
+ public void mouseMove(MouseEvent e) {
+ onMouseMove(e);
+ }
+ });
+
+ addMouseListener(new MouseListener() {
+ public void mouseUp(MouseEvent e) {
+ onMouseUp(e);
+ }
+
+ public void mouseDown(MouseEvent e) {
+ onMouseDown(e);
+ }
+
+ public void mouseDoubleClick(MouseEvent e) {
+ onDoubleClick(e);
}
});
}
+ /**
+ * Sets the result of the layout rendering. The result object indicates if the layout
+ * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
+ *
+ * Implementation detail: the bridge's computeLayout() method already returns a newly
+ * allocated ILayourResult. That means we can keep this result and hold on to it
+ * when it is valid.
+ *
+ * @param result The new rendering result, either valid or not.
+ */
public void setResult(ILayoutResult result) {
- if (result.getSuccess() == ILayoutResult.SUCCESS) {
+
+ // disable any hover
+ mHoverRect = null;
+
+ mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
+
+ if (mIsResultValid && result != null) {
+ mLastValidResult = result;
setImage(result.getImage());
+
+ // Check if the selection is still the same (based on its key)
+ // and eventually recompute its bounds.
+ if (mSelectionViewInfo != null) {
+ ILayoutViewInfo vi = findViewInfoKey(
+ mSelectionViewInfo.getViewKey(),
+ result.getRootView());
+ setSelection(vi);
+ }
}
+
+ redraw();
}
+ //---
+
+ /**
+ * Sets the image of the last *successful* rendering.
+ * Converts the AWT image into an SWT image.
+ */
private void setImage(BufferedImage awtImage) {
- // Convert the AWT image into an SWT image.
int width = awtImage.getWidth();
int height = awtImage.getHeight();
@@ -81,15 +182,207 @@ public class LayoutCanvas extends Canvas {
imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
mImage = new Image(getDisplay(), imageData);
-
- redraw();
}
- private void paint(PaintEvent e) {
+ private void onPaint(PaintEvent e) {
+ GC gc = e.gc;
+
if (mImage != null) {
- GC gc = e.gc;
- gc.drawImage(mImage, 0, 0);
+ if (!mIsResultValid) {
+ gc.setAlpha(128);
+ }
+
+ gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);
+
+ if (!mIsResultValid) {
+ gc.setAlpha(255);
+ }
+ }
+
+ if (mHoverRect != null) {
+ gc.setForeground(mHoverFgColor);
+ gc.setLineStyle(SWT.LINE_DOT);
+ gc.drawRectangle(mHoverRect);
+ }
+
+ // initialize the selection font height once. We need the GC to do that.
+ if (mSelectionFontHeight == 0) {
+ gc.setFont(mSelectionFont);
+ FontMetrics fm = gc.getFontMetrics();
+ mSelectionFontHeight = fm.getHeight();
+ }
+
+ if (mSelectionRect != null) {
+ gc.setForeground(mSelectionFgColor);
+ gc.setLineStyle(SWT.LINE_SOLID);
+ gc.drawRectangle(mSelectionRect);
+
+ if (mSelectionName != null) {
+ int x = mSelectionRect.x + 2;
+ int y = mSelectionRect.y - mSelectionFontHeight;
+ if (y < 0) {
+ y = mSelectionRect.y + mSelectionRect.height;
+ }
+ gc.drawString(mSelectionName, x, y, true /*transparent*/);
+ }
+ }
+ }
+
+ /**
+ * Hover on top of a known child.
+ */
+ private void onMouseMove(MouseEvent e) {
+ if (mLastValidResult != null) {
+ ILayoutViewInfo root = mLastValidResult.getRootView();
+ ILayoutViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN, root);
+
+ // We don't hover on the root since it's not a widget per see and it is always there.
+ if (vi == root) {
+ vi = null;
+ }
+
+ boolean needsUpdate = vi != mHoverViewInfo;
+ mHoverViewInfo = vi;
+
+ mHoverRect = vi == null ? null : getViewInfoRect(vi);
+ if (mHoverRect != null) {
+ mHoverRect.x += IMAGE_MARGIN;
+ mHoverRect.y += IMAGE_MARGIN;
+ }
+
+ if (needsUpdate) {
+ redraw();
+ }
+ }
+ }
+
+ private void onMouseDown(MouseEvent e) {
+ // pass, not used yet.
+ }
+
+ /**
+ * Performs selection on mouse up (not mouse down).
+ */
+ private void onMouseUp(MouseEvent e) {
+ if (mLastValidResult != null) {
+ ILayoutViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN,
+ mLastValidResult.getRootView());
+ setSelection(vi);
+ }
+ }
+
+ private void onDoubleClick(MouseEvent e) {
+ // pass, not used yet.
+ }
+
+ private void setSelection(ILayoutViewInfo viewInfo) {
+ boolean needsUpdate = viewInfo != mSelectionViewInfo;
+ mSelectionViewInfo = viewInfo;
+
+ mSelectionRect = viewInfo == null ? null : getViewInfoRect(viewInfo);
+ if (mSelectionRect != null) {
+ mSelectionRect.x += IMAGE_MARGIN;
+ mSelectionRect.y += IMAGE_MARGIN;
+ }
+
+ String name = viewInfo == null ? null : viewInfo.getName();
+ if (name != null) {
+ // The name is typically a fully-qualified class name. Let's make it a tad shorter.
+
+ if (name.startsWith("android.")) { // $NON-NLS-1$
+ // For android classes, convert android.foo.Name to android...Name
+ int first = name.indexOf('.');
+ int last = name.lastIndexOf('.');
+ if (last > first) {
+ name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
+ }
+ } else {
+ // For custom non-android classes, it's best to keep the 2 first segments of
+ // the namespace, e.g. we want to get something like com.example...MyClass
+ int first = name.indexOf('.');
+ first = name.indexOf('.', first + 1);
+ int last = name.lastIndexOf('.');
+ if (last > first) {
+ name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
+ }
+ }
+ }
+ mSelectionName = name;
+
+ if (needsUpdate) {
+ redraw();
+ }
+ }
+
+ /**
+ * Tries to find a child with the same view key in the view info sub-tree.
+ * Returns null if not found.
+ */
+ private ILayoutViewInfo findViewInfoKey(Object viewKey, ILayoutViewInfo viewInfo) {
+ if (viewInfo.getViewKey() == viewKey) {
+ return viewInfo;
+ }
+
+ // try to find a matching child
+ if (viewInfo.getChildren() != null) {
+ for (ILayoutViewInfo child : viewInfo.getChildren()) {
+ ILayoutViewInfo v = findViewInfoKey(viewKey, child);
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Tries to find the inner most child matching the given x,y coordinates in the view
+ * info sub-tree. This uses the potentially-expanded selection bounds.
+ *
+ * Returns null if not found.
+ */
+ private ILayoutViewInfo findViewInfoAt(int x, int y, ILayoutViewInfo viewInfo) {
+ Rectangle r = getViewInfoRect(viewInfo);
+ if (r.contains(x, y)) {
+
+ // try to find a matching child first
+ if (viewInfo.getChildren() != null) {
+ for (ILayoutViewInfo child : viewInfo.getChildren()) {
+ ILayoutViewInfo v = findViewInfoAt(x, y, child);
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ // if no children matched, this is the view that we're looking for
+ return viewInfo;
}
+
+ return null;
}
+ /**
+ * Returns the bounds of the view info as a rectangle.
+ * In case the view has a null width or null height, it is expanded using
+ * {@link #SELECTION_MARGIN}.
+ */
+ private Rectangle getViewInfoRect(ILayoutViewInfo viewInfo) {
+ int x = viewInfo.getLeft();
+ int y = viewInfo.getTop();
+ int w = viewInfo.getRight() - x;
+ int h = viewInfo.getBottom() - y;
+
+ if (w == 0) {
+ x -= SELECTION_MARGIN;
+ w += 2 * SELECTION_MARGIN;
+ }
+ if (h == 0) {
+ y -= SELECTION_MARGIN;
+ h += 2* SELECTION_MARGIN;
+ }
+
+ return new Rectangle(x, y, w, h);
+ }
}