aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/dictionary.txt3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java65
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java93
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java7
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java9
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java101
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java142
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParserTest.java1
13 files changed, 513 insertions, 26 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 9e0bc97..6da9527 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -28,8 +28,10 @@ codenames
combo
config
configurability
+congrats
coords
ddms
+deactivated
debuggable
dedent
deprecated
@@ -93,6 +95,7 @@ recompilation
redo
regexp
registry
+remap
reparse
reparses
residual
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
index 335e565..0938afa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
@@ -91,11 +91,17 @@ public enum DrawingStyle {
HELP,
/**
- * The style used to raw illegal/error/invalid markers
+ * The style used to draw illegal/error/invalid markers
*/
INVALID,
/**
+ * The style used to draw empty containers of zero bounds (which are padded
+ * a bit to make them visible during a drag or selection).
+ */
+ EMPTY,
+
+ /**
* A style used for unspecified purposes; can be used by a client to have
* yet another color that is domain specific; using this color constant
* rather than your own hardcoded value means that you will be guaranteed to
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 180c329..ed8a205 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
@@ -278,6 +278,13 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
}
/**
+ * Tells the graphical editor to recompute its layout.
+ */
+ public void recomputeLayout() {
+ mGraphicalEditor.recomputeLayout();
+ }
+
+ /**
* Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
*/
@SuppressWarnings("unchecked")
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
index 5f93387..1e04c6b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -37,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,7 +50,7 @@ import java.util.regex.Pattern;
*/
public final class UiElementPullParser extends BasePullParser {
private final static String ATTR_PADDING = "padding"; //$NON-NLS-1$
- private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); //$NON-NLS-1$
+ private final static Pattern FLOAT_PATTERN = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); //$NON-NLS-1$
private final int[] sIntOut = new int[1];
@@ -61,16 +62,52 @@ public final class UiElementPullParser extends BasePullParser {
private List<ElementDescriptor> mLayoutDescriptors;
private final int mDensityValue;
private final float mXdpi;
- private final String mDefaultPaddingValue;
- public UiElementPullParser(UiElementNode top, boolean explodeRendering, int densityValue,
- float xdpi, IProject project) {
+ /**
+ * Number of pixels to pad views with in exploded-rendering mode.
+ */
+ private static final String DEFAULT_PADDING_VALUE =
+ ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$
+
+ /**
+ * Number of pixels to pad exploded individual views with. (This is HALF the width of the
+ * rectangle since padding is repeated on both sides of the empty content.)
+ */
+ private static final String FIXED_PADDING_VALUE = "20px"; //$NON-NLS-1$
+
+ /**
+ * Set of nodes that we want to auto-pad using {@link #FIXED_PADDING_VALUE} as the padding
+ * attribute value. Can be null, which is the case when we don't want to perform any
+ * <b>individual</b> node exploding.
+ */
+ private final Set<UiElementNode> mExplodeNodes;
+
+ /**
+ * Constructs a new {@link UiElementPullParser}, a parser dedicated to the special case of
+ * parsing a layout resource files, and handling "exploded rendering" - adding padding on views
+ * to make them easier to see and operate on.
+ *
+ * @param top The {@link UiElementNode} for the root node.
+ * @param explodeRendering When true, add padding to <b>all</b> nodes in the hierarchy. This
+ * will add rather than replace padding of a node.
+ * @param explodeNodes A set of individual nodes that should be assigned a fixed amount of
+ * padding ({@link #FIXED_PADDING_VALUE}). This is intended for use with nodes that
+ * (without padding) would be invisible. This parameter can be null, in which case
+ * nodes are not individually exploded (but they may all be exploded with the
+ * explodeRendering parameter.
+ * @param densityValue the density factor for the screen.
+ * @param xdpi the screen actual dpi in X
+ * @param project Project containing this layout.
+ */
+ public UiElementPullParser(UiElementNode top, boolean explodeRendering,
+ Set<UiElementNode> explodeNodes,
+ int densityValue, float xdpi, IProject project) {
super();
mRoot = top;
mExplodedRendering = explodeRendering;
+ mExplodeNodes = explodeNodes;
mDensityValue = densityValue;
mXdpi = xdpi;
- mDefaultPaddingValue = ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$
if (mExplodedRendering) {
// get the layout descriptor
IAndroidTarget target = Sdk.getCurrent().getTarget(project);
@@ -255,7 +292,7 @@ public final class UiElementPullParser extends BasePullParser {
public String getAttributeValue(int i) {
if (mZeroAttributeIsPadding) {
if (i == 0) {
- return mDefaultPaddingValue;
+ return DEFAULT_PADDING_VALUE;
} else {
i--;
}
@@ -279,9 +316,17 @@ public final class UiElementPullParser extends BasePullParser {
* This is the main method used by the LayoutInflater to query for attributes.
*/
public String getAttributeValue(String namespace, String localName) {
+ if (mExplodeNodes != null && ATTR_PADDING.equals(localName) &&
+ SdkConstants.NS_RESOURCES.equals(namespace)) {
+ UiElementNode node = getCurrentNode();
+ if (node != null && mExplodeNodes.contains(node)) {
+ return FIXED_PADDING_VALUE;
+ }
+ }
+
if (mZeroAttributeIsPadding && ATTR_PADDING.equals(localName) &&
SdkConstants.NS_RESOURCES.equals(namespace)) {
- return mDefaultPaddingValue;
+ return DEFAULT_PADDING_VALUE;
}
// get the current uiNode
@@ -457,14 +502,14 @@ public final class UiElementPullParser extends BasePullParser {
*/
private boolean stringToPixel(String s) {
// remove the space before and after
- s.trim();
+ s = s.trim();
int len = s.length();
if (len <= 0) {
return false;
}
- // check that there's no non ascii characters.
+ // check that there's no non ASCII characters.
char[] buf = s.toCharArray();
for (int i = 0 ; i < len ; i++) {
if (buf[i] > 255) {
@@ -478,7 +523,7 @@ public final class UiElementPullParser extends BasePullParser {
}
// now look for the string that is after the float...
- Matcher m = sFloatPattern.matcher(s);
+ Matcher m = FLOAT_PATTERN.matcher(s);
if (m.matches()) {
String f_str = m.group(1);
String end = m.group(2);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
index 9714b6c..7485b6d 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
@@ -64,6 +65,13 @@ public class CanvasViewInfo implements IPropertySource {
private final ArrayList<CanvasViewInfo> mChildren = new ArrayList<CanvasViewInfo>();
/**
+ * Is this view info an individually exploded view? This is the case for views
+ * that were specially inflated by the {@link UiElementPullParser} and assigned
+ * fixed padding because they were invisible and somebody requested visibility.
+ */
+ private boolean mExploded;
+
+ /**
* Constructs a {@link CanvasViewInfo} hierarchy based on a given {@link ILayoutViewInfo}
* hierarchy. This call is recursive and builds a full tree.
*
@@ -279,6 +287,45 @@ public class CanvasViewInfo implements IPropertySource {
}
/**
+ * Returns true if this {@link CanvasViewInfo} represents an invisible parent - in
+ * other words, a view that can have children, and that has zero bounds making it
+ * effectively invisible. (We don't actually look for -0- bounds, but
+ * bounds smaller than SELECTION_MIN_SIZE.)
+ *
+ * @return True if this is an invisible parent.
+ */
+ public boolean isInvisibleParent() {
+ if (mAbsRect.width < SELECTION_MIN_SIZE || mAbsRect.height < SELECTION_MIN_SIZE) {
+ return mUiViewKey != null && mUiViewKey.getDescriptor().hasChildren();
+ }
+
+ return false;
+ }
+
+ /**
+ * Is this {@link CanvasViewInfo} a view that has had its padding inflated in order to
+ * make it visible during selection or dragging? Note that this is NOT considered to
+ * be the case in the explode-all-views mode where all nodes have their padding
+ * increased; it's only used for views that individually exploded because they were
+ * requested visible and they returned true for {@link #isInvisibleParent()}.
+ *
+ * @return True if this is an exploded node.
+ */
+ public boolean isExploded() {
+ return mExploded;
+ }
+
+ /**
+ * Mark this {@link CanvasViewInfo} as having been exploded or not. See the
+ * {@link #isExploded()} method for details on what this property means.
+ *
+ * @param exploded New value of the exploded property to mark this info with.
+ */
+ /* package */ void setExploded(boolean exploded) {
+ this.mExploded = exploded;
+ }
+
+ /**
* Returns the info represented as a {@link SimpleElement}.
*
* @return A {@link SimpleElement} wrapping this info.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java
new file mode 100644
index 0000000..946c381
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * The {@link EmptyViewsOverlay} paints bounding rectangles for any of the empty and
+ * invisible container views in the scene.
+ */
+public class EmptyViewsOverlay extends Overlay {
+ /** The {@link ViewHierarchy} containing visible view information. */
+ private final ViewHierarchy mViewHierarchy;
+
+ /** Border color to paint the bounding boxes with. */
+ private Color mBorderColor;
+
+ /** Vertical scaling & scrollbar information. */
+ private ScaleInfo mVScale;
+
+ /** Horizontal scaling & scrollbar information. */
+ private ScaleInfo mHScale;
+
+ /**
+ * Constructs a new {@link EmptyViewsOverlay} linked to the given view hierarchy.
+ *
+ * @param viewHierarchy The {@link ViewHierarchy} to render.
+ * @param hScale The {@link ScaleInfo} to use to transfer horizontal layout
+ * coordinates to screen coordinates.
+ * @param vScale The {@link ScaleInfo} to use to transfer vertical layout coordinates
+ * to screen coordinates.
+ */
+ public EmptyViewsOverlay(ViewHierarchy viewHierarchy, ScaleInfo hScale, ScaleInfo vScale) {
+ super();
+ this.mViewHierarchy = viewHierarchy;
+ this.mHScale = hScale;
+ this.mVScale = vScale;
+ }
+
+ @Override
+ public void create(Device device) {
+ mBorderColor = new Color(device, SwtDrawingStyle.EMPTY.getStrokeColor());
+ }
+
+ @Override
+ public void dispose() {
+ if (mBorderColor != null) {
+ mBorderColor.dispose();
+ mBorderColor = null;
+ }
+ }
+
+ @Override
+ public void paint(GC gc) {
+ gc.setForeground(mBorderColor);
+ gc.setLineDash(null);
+ gc.setLineStyle(SwtDrawingStyle.EMPTY.getLineStyle());
+ int oldAlpha = gc.getAlpha();
+ gc.setAlpha(SwtDrawingStyle.EMPTY.getStrokeAlpha());
+ gc.setLineWidth(SwtDrawingStyle.EMPTY.getLineWidth());
+
+ for (CanvasViewInfo info : mViewHierarchy.getInvisibleViews()) {
+ Rectangle r = info.getAbsRect();
+
+ int x = mHScale.translate(r.x);
+ int y = mVScale.translate(r.y);
+ int w = mHScale.scale(r.width);
+ int h = mVScale.scale(r.height);
+
+ // +1: See explanation in equivalent code in {@link OutlineOverlay#paint}
+ gc.drawRectangle(x, y, w + 1, h + 1);
+ }
+
+ gc.setAlpha(oldAlpha);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
index 97a21ee..9da3599 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
@@ -370,6 +370,8 @@ public class GestureManager {
* The cursor has entered the drop target boundaries. {@inheritDoc}
*/
public void dragEnter(DropTargetEvent event) {
+ mCanvas.showInvisibleViews(true);
+
if (mCurrentGesture == null) {
Gesture newGesture = mZombieGesture;
if (newGesture == null) {
@@ -406,6 +408,8 @@ public class GestureManager {
finishGesture(ControlPoint.create(mCanvas, event), true);
mZombieGesture = dropGesture;
}
+
+ mCanvas.showInvisibleViews(false);
}
/**
@@ -477,7 +481,6 @@ public class GestureManager {
// element, *change* the selection to match the element under the
// cursor and use that. If nothing can be selected, abort the drag
// operation.
-
List<CanvasSelection> selections = mCanvas.getSelectionManager().getSelections();
mDragSelection.clear();
@@ -559,6 +562,7 @@ public class GestureManager {
e.doit = false;
} else {
// Otherwise, the drag means you are moving something
+ mCanvas.showInvisibleViews(true);
startGesture(ControlPoint.create(mCanvas, e), new MoveGesture(mCanvas), 0);
}
@@ -601,6 +605,7 @@ public class GestureManager {
GlobalCanvasDragInfo.getInstance().stopDrag();
finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE);
+ mCanvas.showInvisibleViews(false);
mCanvas.redraw();
}
}
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 a0fbac6..b0d5ed9 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
@@ -1084,7 +1084,7 @@ public class GraphicalEditorPart extends EditorPart
}
};
- mCanvasViewer.getCanvas().setResult(result);
+ mCanvasViewer.getCanvas().setResult(result, null /*explodeNodes*/);
return;
}
@@ -1209,8 +1209,11 @@ public class GraphicalEditorPart extends EditorPart
float ydpi = mConfigComposite.getYDpi();
boolean isProjectTheme = mConfigComposite.isProjectTheme();
+ LayoutCanvas canvas = getCanvasControl();
+ Set<UiElementNode> explodeNodes = canvas.getNodesToExplode();
+
UiElementPullParser parser = new UiElementPullParser(getModel(),
- mUseExplodeMode, density, xdpi, iProject);
+ mUseExplodeMode, explodeNodes, density, xdpi, iProject);
ILayoutResult result = computeLayout(bridge, parser,
iProject /* projectKey */,
@@ -1223,7 +1226,7 @@ public class GraphicalEditorPart extends EditorPart
// post rendering clean up
bridge.cleanUp();
- mCanvasViewer.getCanvas().setResult(result);
+ canvas.setResult(result, explodeNodes);
// update the UiElementNode with the layout info.
if (result.getSuccess() != ILayoutResult.SUCCESS) {
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 c97013b..1c0c798 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
@@ -71,6 +71,10 @@ import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.w3c.dom.Node;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
/**
* Displays the image rendered by the {@link GraphicalEditorPart} and handles
* the interaction with the widgets.
@@ -110,6 +114,9 @@ class LayoutCanvas extends Canvas {
/** When true, always display the outline of all views. */
private boolean mShowOutline;
+ /** When true, display the outline of all empty parent views. */
+ private boolean mShowInvisible;
+
/** Drop target associated with this composite. */
private DropTarget mDropTarget;
@@ -161,6 +168,9 @@ class LayoutCanvas extends Canvas {
/** The overlay which paints the optional outline. */
private OutlineOverlay mOutlineOverlay;
+ /** The overlay which paints outlines around empty children */
+ private EmptyViewsOverlay mEmptyOverlay;
+
/** The overlay which paints the mouse hover. */
private HoverOverlay mHoverOverlay;
@@ -199,7 +209,7 @@ class LayoutCanvas extends Canvas {
mFont = display.getSystemFont();
// --- Set up graphic overlays
- // mOutlineOverlay is initialized lazily
+ // mOutlineOverlay and mEmptyOverlay are initialized lazily
mHoverOverlay = new HoverOverlay(mHScale, mVScale);
mHoverOverlay.create(display);
mSelectionOverlay = new SelectionOverlay();
@@ -300,6 +310,11 @@ class LayoutCanvas extends Canvas {
mOutlineOverlay = null;
}
+ if (mEmptyOverlay != null) {
+ mEmptyOverlay.dispose();
+ mEmptyOverlay = null;
+ }
+
if (mHoverOverlay != null) {
mHoverOverlay.dispose();
mHoverOverlay = null;
@@ -427,12 +442,17 @@ class LayoutCanvas extends Canvas {
* when it is valid.
*
* @param result The new rendering result, either valid or not.
+ * @param explodedNodes The set of individual nodes the layout computer was asked to
+ * explode. Note that these are independent of the explode-all mode where
+ * all views are exploded; this is used only for the mode (
+ * {@link #showInvisibleViews(boolean)}) where individual invisible nodes
+ * are padded during certain interactions.
*/
- /* package */ void setResult(ILayoutResult result) {
+ /* package */ void setResult(ILayoutResult result, Set<UiElementNode> explodedNodes) {
// disable any hover
clearHover();
- mViewHierarchy.setResult(result);
+ mViewHierarchy.setResult(result, explodedNodes);
if (mViewHierarchy.isValid() && result != null) {
Image image = mImageOverlay.setImage(result.getImage());
@@ -532,6 +552,14 @@ class LayoutCanvas extends Canvas {
mOutlineOverlay.paint(gc);
}
+ if (mShowInvisible) {
+ if (mEmptyOverlay == null) {
+ mEmptyOverlay = new EmptyViewsOverlay(mViewHierarchy, mHScale, mVScale);
+ mEmptyOverlay.create(getDisplay());
+ }
+ mEmptyOverlay.paint(gc);
+ }
+
mHoverOverlay.paint(gc);
mSelectionOverlay.paint(mSelectionManager, gc, mGCWrapper, mRulesEngine);
mGestureManager.paint(gc);
@@ -542,6 +570,72 @@ class LayoutCanvas extends Canvas {
}
/**
+ * Shows or hides invisible parent views, which are views which have empty bounds and
+ * no children. The nodes which will be shown are provided by
+ * {@link #getNodesToExplode()}.
+ *
+ * @param show When true, any invisible parent nodes are padded and highlighted
+ * ("exploded"), and when false any formerly exploded nodes are hidden.
+ */
+ /* package */ void showInvisibleViews(boolean show) {
+ if (mShowInvisible == show) {
+ return;
+ }
+
+ // Optimization: Avoid doing work when we don't have invisible parents (on show)
+ // or formerly exploded nodes (on hide).
+ if (show && !mViewHierarchy.hasInvisibleParents()) {
+ return;
+ } else if (!show && !mViewHierarchy.hasExplodedParents()) {
+ return;
+ }
+
+ mShowInvisible = show;
+ mLayoutEditor.recomputeLayout();
+ }
+
+ /**
+ * Returns a set of nodes that should be exploded (forced non-zero padding during render),
+ * or null if no nodes should be exploded. (Note that this is independent of the
+ * explode-all mode, where all nodes are padded -- that facility does not use this
+ * mechanism, which is only intended to be used to expose invisible parent nodes.
+ *
+ * @return The set of invisible parents, or null if no views should be expanded.
+ */
+ public Set<UiElementNode> getNodesToExplode() {
+ if (mShowInvisible) {
+ return mViewHierarchy.getInvisibleNodes();
+ }
+
+ // IF we have selection, and IF we have invisible nodes in the view,
+ // see if any of the selected items are among the invisible nodes, and if so
+ // add them to a lazily constructed set which we pass back for rendering.
+ Set<UiElementNode> result = null;
+ List<CanvasSelection> selections = mSelectionManager.getSelections();
+ if (selections.size() > 0) {
+ List<CanvasViewInfo> invisibleParents = mViewHierarchy.getInvisibleViews();
+ if (invisibleParents.size() > 0) {
+ for (CanvasSelection item : selections) {
+ CanvasViewInfo viewInfo = item.getViewInfo();
+ // O(n^2) here, but both the selection size and especially the
+ // invisibleParents size are expected to be small
+ if (invisibleParents.contains(viewInfo)) {
+ UiViewElementNode node = viewInfo.getUiViewKey();
+ if (node != null) {
+ if (result == null) {
+ result = new HashSet<UiElementNode>();
+ }
+ result.add(node);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Clears the hover.
*/
/* package */ void clearHover() {
@@ -889,5 +983,4 @@ class LayoutCanvas extends Canvas {
AdtPlugin.printToConsole("Canvas", String.format(message, params));
}
}
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
index affd69a..3db65f1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
@@ -180,6 +180,7 @@ public class SelectionManager implements ISelectionProvider {
}
boolean changed = false;
+ boolean redoLayout = false;
// Create a list of all currently selected view infos
Set<CanvasViewInfo> oldSelected = new HashSet<CanvasViewInfo>();
@@ -206,15 +207,24 @@ public class SelectionManager implements ISelectionProvider {
mSelections.add(createSelection(newVi));
changed = true;
}
+ if (newVi.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
}
// Deselect old selected items that are not in the new one
for (CanvasViewInfo vi : oldSelected) {
+ if (vi.isExploded()) {
+ redoLayout = true;
+ }
deselect(vi);
changed = true;
}
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
if (changed) {
redraw();
updateMenuActions();
@@ -238,7 +248,6 @@ public class SelectionManager implements ISelectionProvider {
* is a plain select or a toggle, etc.
*/
public void select(MouseEvent e) {
-
boolean isMultiClick = (e.stateMask & SWT.SHIFT) != 0 ||
// On Mac, the Command key is the normal toggle accelerator
((SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) &&
@@ -284,6 +293,10 @@ public class SelectionManager implements ISelectionProvider {
if (vi != null) {
// toggle this selection on-off: remove it if already selected
if (deselect(vi)) {
+ if (vi.isExploded()) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
return;
}
@@ -334,7 +347,6 @@ public class SelectionManager implements ISelectionProvider {
} else {
// Case where no modifier is pressed: either select or reset the selection.
-
selectSingle(vi);
}
}
@@ -349,6 +361,8 @@ public class SelectionManager implements ISelectionProvider {
// reset alternate selection if any
mAltSelection = null;
+ boolean redoLayout = hasExplodedItems();
+
// reset (multi)selection if any
if (!mSelections.isEmpty()) {
if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
@@ -360,11 +374,30 @@ public class SelectionManager implements ISelectionProvider {
if (vi != null) {
mSelections.add(createSelection(vi));
+ if (vi.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
fireSelectionChanged();
+
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
}
+ /** Returns true if the view hierarchy is showing exploded items. */
+ private boolean hasExplodedItems() {
+ for (CanvasSelection item : mSelections) {
+ if (item.getViewInfo().isExploded()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Selects the given set of {@link CanvasViewInfo}s. This is similar to
* {@link #selectSingle} but allows you to make a multi-selection. Issues a
@@ -377,14 +410,24 @@ public class SelectionManager implements ISelectionProvider {
// reset alternate selection if any
mAltSelection = null;
+ boolean redoLayout = hasExplodedItems();
+
mSelections.clear();
if (viewInfos != null) {
for (CanvasViewInfo viewInfo : viewInfos) {
mSelections.add(createSelection(viewInfo));
+ if (viewInfo.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
}
fireSelectionChanged();
+
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
index 36fdad9..2748297 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
@@ -90,6 +90,11 @@ public enum SwtDrawingStyle {
INVALID(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0xFF, 0x00, 0x00), 150, 2, SWT.LINE_SOLID),
/**
+ * The style definition corresponding to {@link DrawingStyle#EMPTY}
+ */
+ EMPTY(new RGB(0xFF, 0xFF, 0x55), 255, new RGB(0xFF, 0xFF, 0x55), 255, 1, SWT.LINE_DASH),
+
+ /**
* The style definition corresponding to {@link DrawingStyle#CUSTOM1}
*/
CUSTOM1(new RGB(0xFF, 0x00, 0xFF), 255, null, 0, 1, SWT.LINE_SOLID),
@@ -183,6 +188,8 @@ public enum SwtDrawingStyle {
/**
* Return the corresponding SwtDrawingStyle for the given
* {@link DrawingStyle}
+ * @param style The style to convert from a {@link DrawingStyle} to a {@link SwtDrawingStyle}.
+ * @return A corresponding {@link SwtDrawingStyle}.
*/
public static SwtDrawingStyle of(DrawingStyle style) {
switch (style) {
@@ -208,6 +215,8 @@ public enum SwtDrawingStyle {
return HELP;
case INVALID:
return INVALID;
+ case EMPTY:
+ return EMPTY;
case CUSTOM1:
return CUSTOM1;
case CUSTOM2:
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index 24d0caa..8c39662 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.INode;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.layoutlib.api.ILayoutResult;
import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
@@ -27,7 +28,10 @@ import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* The view hierarchy class manages a set of view info objects and performs find
@@ -48,7 +52,7 @@ public class ViewHierarchy {
}
/**
- * The CanvasViewInfo root created by the last call to {@link #setResult(ILayoutResult)}
+ * The CanvasViewInfo root created by the last call to {@link #setResult}
* with a valid layout.
* <p/>
* This <em>can</em> be null to indicate we're dealing with an empty document with
@@ -59,7 +63,7 @@ public class ViewHierarchy {
private CanvasViewInfo mLastValidViewInfoRoot;
/**
- * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}.
+ * True when the last {@link #setResult} provided a valid {@link ILayoutResult}.
* <p/>
* When false this means the canvas is displaying an out-dated result image & bounds and some
* features should be disabled accordingly such a drag'n'drop.
@@ -70,6 +74,27 @@ public class ViewHierarchy {
private boolean mIsResultValid;
/**
+ * A list of invisible parents (see {@link CanvasViewInfo#isInvisibleParent()} for
+ * details) in the current view hierarchy.
+ */
+ private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>();
+
+ /**
+ * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it
+ * reflects updates to the underlying {@link #mInvisibleParents} list.
+ */
+ private final List<CanvasViewInfo> mInvisibleParentsReadOnly =
+ Collections.unmodifiableList(mInvisibleParents);
+
+ /**
+ * Flag which records whether or not we have any exploded parent nodes in this
+ * view hierarchy. This is used to track whether or not we need to recompute the
+ * layout when we exit show-all-invisible-parents mode (see
+ * {@link LayoutCanvas#showInvisibleViews}).
+ */
+ private boolean mExplodedParents;
+
+ /**
* 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.
*
@@ -78,9 +103,15 @@ public class ViewHierarchy {
* when it is valid.
*
* @param result The new rendering result, either valid or not.
+ * @param explodedNodes The set of individual nodes the layout computer was asked to
+ * explode. Note that these are independent of the explode-all mode where
+ * all views are exploded; this is used only for the mode (
+ * {@link LayoutCanvas#showInvisibleViews}) where individual invisible
+ * nodes are padded during certain interactions.
*/
- /* package */ void setResult(ILayoutResult result) {
+ /* package */ void setResult(ILayoutResult result, Set<UiElementNode> explodedNodes) {
mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
+ mExplodedParents = false;
if (mIsResultValid && result != null) {
ILayoutViewInfo root = result.getRootView();
@@ -92,8 +123,18 @@ public class ViewHierarchy {
updateNodeProxies(mLastValidViewInfoRoot);
+ // Update the data structures related to tracking invisible and exploded nodes.
+ // We need to find the {@link CanvasViewInfo} objects that correspond to
+ // the passed in {@link UiElementNode} keys that were re-rendered, and mark
+ // them as exploded and store them in a list for rendering.
+ mExplodedParents = false;
+ mInvisibleParents.clear();
+ addInvisibleParents(mLastValidViewInfoRoot, explodedNodes);
+
// Update the selection
mCanvas.getSelectionManager().sync(mLastValidViewInfoRoot);
+ } else {
+ mInvisibleParents.clear();
}
}
@@ -122,10 +163,45 @@ public class ViewHierarchy {
}
}
+ /**
+ * Make a pass over the view hierarchy and look for two things:
+ * <ol>
+ * <li>Invisible parents. These are nodes that can hold children and have empty
+ * bounds. These are then added to the {@link #mInvisibleParents} list.
+ * <li>Exploded nodes. These are nodes that were previously marked as invisible, and
+ * subsequently rendered by a recomputed layout. They now no longer have empty bounds,
+ * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we
+ * for example in selection operations can determine if we need to recompute the
+ * layout.
+ * </ol>
+ *
+ * @param vi
+ * @param invisibleNodes
+ */
+ private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) {
+ if (vi == null) {
+ return;
+ }
+
+ if (vi.isInvisibleParent()) {
+ mInvisibleParents.add(vi);
+ } else if (invisibleNodes != null) {
+ UiViewElementNode key = vi.getUiViewKey();
+ if (key != null && invisibleNodes.contains(key)) {
+ vi.setExploded(true);
+ mExplodedParents = true;
+ mInvisibleParents.add(vi);
+ }
+ }
+
+ for (CanvasViewInfo child : vi.getChildren()) {
+ addInvisibleParents(child, invisibleNodes);
+ }
+ }
/**
- * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
+ * Returns true when the last {@link #setResult} provided a valid
* {@link ILayoutResult}.
* <p/>
* When false this means the canvas is displaying an out-dated result image & bounds and some
@@ -147,6 +223,24 @@ public class ViewHierarchy {
return mLastValidViewInfoRoot == null;
}
+ /**
+ * Returns true if we have parents in this hierarchy that are invisible (e.g. because
+ * they have no children and zero layout bounds).
+ *
+ * @return True if we have invisible parents.
+ */
+ public boolean hasInvisibleParents() {
+ return mInvisibleParents.size() > 0;
+ }
+
+ /**
+ * Returns true if we have views that were exploded during rendering
+ * @return True if we have exploded parents
+ */
+ public boolean hasExplodedParents() {
+ return mExplodedParents;
+ }
+
/** Locates and return any views that overlap the given selection rectangle.
* @param topLeft The top left corner of the selection rectangle.
* @param bottomRight The bottom right corner of the selection rectangle.
@@ -411,7 +505,7 @@ public class ViewHierarchy {
}
/**
- * Return the root of the view hierarchy, if any (could be null, for example
+ * Returns the root of the view hierarchy, if any (could be null, for example
* on rendering failure).
*
* @return The current view hierarchy, or null
@@ -420,4 +514,42 @@ public class ViewHierarchy {
return mLastValidViewInfoRoot;
}
+ /**
+ * Returns a collection of views that have zero bounds and that correspond to empty
+ * parents. Note that the views may not actually have zero bounds; in particular, if
+ * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the
+ * bounds of a shown invisible node. Therefore, this method returns the views that
+ * would be invisible in a real rendering of the scene.
+ *
+ * @return A collection of empty parent views.
+ */
+ public List<CanvasViewInfo> getInvisibleViews() {
+ return mInvisibleParentsReadOnly;
+ }
+
+ /**
+ * Returns the invisible nodes (the {@link UiElementNode} objects corresponding
+ * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}.
+ * We are pulling out the nodes since they preserve their identity across layout
+ * rendering, and in particular we return it as a set such that the layout renderer
+ * can perform quick identity checks when looking up attribute values during the
+ * rendering process.
+ *
+ * @return A set of the invisible nodes.
+ */
+ public Set<UiElementNode> getInvisibleNodes() {
+ if (mInvisibleParents.size() == 0) {
+ return Collections.emptySet();
+ }
+
+ Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size());
+ for (CanvasViewInfo info : mInvisibleParents) {
+ UiViewElementNode node = info.getUiViewKey();
+ if (node != null) {
+ nodes.add(node);
+ }
+ }
+
+ return nodes;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParserTest.java
index 68e31d5..f857126 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParserTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParserTest.java
@@ -170,6 +170,7 @@ public class UiElementPullParserTest extends TestCase {
UiElementPullParser parser = new UiElementPullParser(
ui, // model
false, // explodedView
+ null, // explodeNodes
Density.MEDIUM.getDpiValue(), // density (default from ConfigurationComposite)
Density.MEDIUM.getDpiValue(), // xdpi (default from ConfigurationComposite)
null // iProject