aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2010-06-21 13:09:23 -0700
committerAndroid Code Review <code-review@android.com>2010-06-21 13:09:23 -0700
commit13b97d8b6a4137d9be28e60196eb6d44dee742fa (patch)
tree426093426c1b67c51fd67a16a2f440c12f20cc3d
parente48a35070a0b27ec8ea99e2cd3bf999a11b175b5 (diff)
parent8c7e29f5cb9f1200959f3beb5aa03eacf210b004 (diff)
downloadsdk-13b97d8b6a4137d9be28e60196eb6d44dee742fa.zip
sdk-13b97d8b6a4137d9be28e60196eb6d44dee742fa.tar.gz
sdk-13b97d8b6a4137d9be28e60196eb6d44dee742fa.tar.bz2
Merge changes Ib9f3473d,Ic9ac3d34,I1e26f9c5,I64959e27
* changes: ADT GLE2: cleanup some constants and review feedback. ADT GLE2: drag'n'drop in RelativeLayout. ADT GLE2: readme (first quick'n'dirty draft) ADT GLE2: drag'n'drop for LinearLayout
-rwxr-xr-xdocs/gscripts.txt49
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseLayout.groovy254
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseView.groovy18
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy162
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy188
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy4
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.RelativeLayout.groovy206
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropFeedback.java13
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IDragElement.java20
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java27
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java4
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java9
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java97
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java6
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java5
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java36
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java53
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java28
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java6
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElementTest.java119
20 files changed, 949 insertions, 355 deletions
diff --git a/docs/gscripts.txt b/docs/gscripts.txt
new file mode 100755
index 0000000..e3f5d18
--- /dev/null
+++ b/docs/gscripts.txt
@@ -0,0 +1,49 @@
+This file describes the "gscripts" folder in ADT (Android Eclipse Plugin).
+
+
+----------
+- Overview
+----------
+
+ADT is the Android Eclipse Plugin. The plugin delivers a new editor, called
+the the Graphical Layout Editor (a.k.a. GLE2), to visually edit Android layout
+XML files.
+
+Details on how to handle the various Android views and layouts is not
+hardcoded in the GLE2 itself. Instead it is differed to a bunch of Groovy
+scripts.
+
+
+(TODO: expand/replace with a better overview of implementation... goal is
+to use this a doc for 3rd-party projects to implement their own rules.)
+
+
+
+-------------
+- Groovy tips
+-------------
+
+
+- Debugging:
+
+If you run ADT in debug mode and want to trace into Groovy
+methods, you need to tell Eclipse where to find the Groovy source code.
+
+To do this:
+- in Eclipse, import an existing project
+- Select the project at <android-source-tree>/prebuilt/common/groovy/
+- This will add a new Eclipse project named "GroovySrc" which contains
+ a single zip file with the groovy source.
+- ADT is already pre-configured to find the Groovy source in the GroovySrc
+ project.
+
+
+
+- Private methods:
+
+Be careful when adding new helper methods in the BaseView
+or BaseLayout classes.
+
+Due to the way Groovy looks up methods, private methods will *not* be found by
+same-class methods if invoked by a derived class in the context of a closure
+(which is about the case of all these helper methods.)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseLayout.groovy
index 422dc70..98e0b0f 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseLayout.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseLayout.groovy
@@ -26,4 +26,258 @@ public class BaseLayout extends BaseView {
super.onDispose();
}
+ // ==== Utility methods used by derived layouts ====
+
+ // TODO revisit.
+ protected String[] getLayoutAttrFilter() {
+ return [
+ // from AbsoluteLayout
+ "layout_x",
+ "layout_y",
+
+ // from RelativeLayout
+ "layout_above",
+ "layout_below",
+ "layout_toLeftOf",
+ "layout_toRightOf",
+ "layout_alignBaseline",
+ "layout_alignTop",
+ "layout_alignBottom",
+ "layout_alignLeft",
+ "layout_alignRight",
+ "layout_alignParentTop",
+ "layout_alignParentBottom",
+ "layout_alignParentLeft",
+ "layout_alignParentRight",
+ "layout_alignWithParentMissing",
+ "layout_centerHorizontal",
+ "layout_centerInParent",
+ "layout_centerVertical",
+ ];
+ }
+
+ /**
+ * Draws the bounds of the given elements and all its children elements
+ * in the canvas with the specified offet.
+ */
+ protected void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) {
+ Rect b = element.getBounds();
+ if (b.isValid()) {
+ b = b.copy().offsetBy(offsetX, offsetY);
+ gc.drawRect(b);
+ }
+
+ for(inner in element.getInnerElements()) {
+ drawElement(gc, inner, offsetX, offsetY);
+ }
+ }
+
+ /**
+ * Collect all the "android:id" IDs from the dropped elements.
+ *
+ * When moving objects within the same canvas, that's all there is to do.
+ * However if the objects are moved to a different canvas or are copied
+ * then set createNewIds to true to find the existing IDs under targetNode
+ * and create a map with new non-conflicting unique IDs as needed.
+ *
+ * Returns a map String old-id => tuple (String new-id, String fqcn)
+ * where fqcn is the FQCN of the element.
+ */
+ protected Map getDropIdMap(INode targetNode,
+ IDragElement[] elements,
+ boolean createNewIds) {
+ def idMap = [:];
+
+ if (createNewIds) {
+ collectIds(idMap, elements);
+ // Need to remap ids if necessary
+ idMap = remapIds(targetNode, idMap);
+ }
+
+ return idMap;
+ }
+
+
+ /**
+ * Fills idMap with a map String id => tuple (String id, String fqcn)
+ * where fqcn is the FQCN of the element (in case we want to generate
+ * new IDs based on the element type.)
+ *
+ * @see #getDropIdMap
+ */
+ protected Map collectIds(Map idMap, IDragElement[] elements) {
+ for (element in elements) {
+ def attr = element.getAttribute(ANDROID_URI, ATTR_ID);
+ if (attr != null) {
+ String id = attr.getValue();
+ if (id != null && id != "") {
+ idMap.put(id, [id, element.getFqcn()]);
+ }
+ }
+
+ collectIds(idMap, element.getInnerElements());
+ }
+
+ return idMap;
+ }
+
+ /**
+ * Used by #getDropIdMap to find new IDs in case of conflict.
+ */
+ protected Map remapIds(INode node, Map idMap) {
+ // Visit the document to get a list of existing ids
+ def existingIdMap = [:];
+ collectExistingIds(node.getRoot(), existingIdMap);
+
+ def new_map = [:];
+ idMap.each() { key, value ->
+ def id = normalizeId(key);
+
+ if (!existingIdMap.containsKey(id)) {
+ // Not a conflict. Use as-is.
+ new_map.put(key, value);
+ if (key != id) {
+ new_map.put(id, value);
+ }
+ } else {
+ // There is a conflict. Get a new id.
+ def new_id = findNewId(value[1], existingIdMap);
+ value[0] = new_id;
+ new_map.put(id, value);
+ new_map.put(id.replaceFirst("@\\+", "@"), value);
+ }
+ }
+
+ return new_map;
+ }
+
+ /**
+ * Used by #remapIds to find a new ID for a conflicting element.
+ */
+ protected String findNewId(String fqcn, Map existingIdMap) {
+ // Get the last component of the FQCN (e.g. "android.view.Button" => "Button")
+ String name = fqcn[fqcn.lastIndexOf(".")+1 .. fqcn.length()-1];
+
+ for (int i = 1; i < 1000000; i++) {
+ String id = String.format("@+id/%s%02d", name, i);
+ if (!existingIdMap.containsKey(id)) {
+ existingIdMap.put(id, id);
+ return id;
+ }
+ }
+
+ // We'll never reach here.
+ return null;
+ }
+
+ /**
+ * Used by #getDropIdMap to find existing IDs recursively.
+ */
+ protected void collectExistingIds(INode root, Map existingIdMap) {
+ if (root == null) {
+ return;
+ }
+
+ def id = root.getStringAttr(ANDROID_URI, ATTR_ID);
+ if (id != null) {
+ id = normalizeId(id);
+
+ if (!existingIdMap.containsKey(id)) {
+ existingIdMap.put(id, id);
+ }
+ }
+
+ for(child in root.getChildren()) {
+ collectExistingIds(child, existingIdMap);
+ }
+ }
+
+ /**
+ * Transforms @id/name into @+id/name to treat both forms the same way.
+ */
+ protected String normalizeId(String id) {
+ if (id.indexOf("@+") == -1) {
+ id = id.replaceFirst("@", "@+");
+ }
+ return id;
+ }
+
+ /**
+ * Copies all the attributes from oldElement to newNode.
+ *
+ * Uses the idMap to transform the value of all attributes of Format.REFERENCE,
+ * If filter is non-null, it's a closure that takes for argument:
+ * String attribue-uri (namespace), String attribute-name, String attribute-value
+ * The closure should return a valid replacement string.
+ * The closure can return either null, false or an empty string to prevent the attribute
+ * from being copied into the new node.
+ */
+ protected void addAttributes(INode newNode, IDragElement oldElement,
+ Map idMap, Closure filter) {
+
+ // A little trick here: when creating new UI widgets by dropping them from
+ // the palette, we assign them a new id and then set the text attribute
+ // to that id, so for example a Button will have android:text="@+id/Button01".
+ // Here we detect if such an id is being remapped to a new id and if there's
+ // a text attribute with exactly the same id name, we update it too.
+ String oldText = null;
+ String oldId = null;
+ String newId = null;
+
+ for (attr in oldElement.getAttributes()) {
+ String uri = attr.getUri();
+ String name = attr.getName();
+ String value = attr.getValue();
+
+ if (uri == ANDROID_URI) {
+ if (name == ATTR_ID) {
+ oldId = value;
+ } else if (name == ATTR_TEXT) {
+ oldText = value;
+ }
+ }
+
+ def attrInfo = newNode.getAttributeInfo(uri, name);
+ if (attrInfo != null) {
+ def formats = attrInfo.getFormats();
+ if (formats != null && IAttributeInfo.Format.REFERENCE in formats) {
+ if (idMap.containsKey(value)) {
+ value = idMap[value][0];
+ }
+ }
+ }
+
+ if (filter != null) {
+ value = filter(uri, name, value);
+ }
+ if (value != null && value != false && value != "") {
+ newNode.setAttribute(uri, name, value);
+
+ if (uri == ANDROID_URI && name == ATTR_ID && oldId != null && value != oldId) {
+ newId = value;
+ }
+ }
+ }
+
+ if (newId != null && oldText == oldId) {
+ newNode.setAttribute(ANDROID_URI, ATTR_TEXT, newId);
+ }
+ }
+
+ /**
+ * Adds all the children elements of oldElement to newNode, recursively.
+ * Attributes are adjusted by calling addAttributes with idMap as necessary, with
+ * no closure filter.
+ */
+ protected void addInnerElements(INode newNode, IDragElement oldElement, Map idMap) {
+
+ for (element in oldElement.getInnerElements()) {
+ String fqcn = element.getFqcn();
+ INode childNode = newNode.appendChild(fqcn);
+
+ addAttributes(childNode, element, idMap, null /* closure */);
+ addInnerElements(childNode, element, idMap);
+ }
+ }
+
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseView.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseView.groovy
index dc37ffe..8a479d0 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseView.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/BaseView.groovy
@@ -20,11 +20,24 @@ public class BaseView implements IViewRule {
private String mFqcn;
+ // Some common Android layout attribute names used by the view rules.
+ // All these belong to the attribute namespace ANDROID_URI.
+ public static String ATTR_ID = "id";
+ public static String ATTR_TEXT = "text";
+ public static String ATTR_LAYOUT_WIDTH = "layout_width";
+ public static String ATTR_LAYOUT_HEIGHT = "layout_height";
+
+ // Some common Android layout attribute values used by the view rules.
+ public static String VALUE_FILL_PARENT = "fill_parent";
+ public static String VALUE_MATCH_PARENT = "match_parent";
+ public static String VALUE_MATCH_CONTENT = "match_content";
+
+
/**
* Namespace for the Android resource XML,
* i.e. "http://schemas.android.com/apk/res/android"
*/
- public static ANDROID_URI = "http://schemas.android.com/apk/res/android";
+ public static String ANDROID_URI = "http://schemas.android.com/apk/res/android";
public boolean onInitialize(String fqcn) {
// This base rule can handle any class.
@@ -114,8 +127,7 @@ public class BaseView implements IViewRule {
// ignore
}
- void onDropped(INode targetNode, IDragElement[] elements, DropFeedback feedback,
- Point p, boolean isCopy, boolean sameCanvas) {
+ void onDropped(INode targetNode, IDragElement[] elements, DropFeedback feedback, Point p) {
// ignore
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy
index c417ccb..ad3c862 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy
@@ -86,18 +86,6 @@ public class AndroidWidgetAbsoluteLayoutRule extends BaseLayout {
}
}
- void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) {
- Rect b = element.getBounds();
- if (b.isValid()) {
- b = b.copy().offsetBy(offsetX, offsetY);
- gc.drawRect(b);
- }
-
- element.getInnerElements().each {
- drawElement(gc, it, offsetX, offsetY);
- }
- }
-
DropFeedback onDropMove(INode targetNode,
IDragElement[] elements,
DropFeedback feedback,
@@ -116,9 +104,7 @@ public class AndroidWidgetAbsoluteLayoutRule extends BaseLayout {
void onDropped(INode targetNode,
IDragElement[] elements,
DropFeedback feedback,
- Point p,
- boolean isCopy,
- boolean sameCanvas) {
+ Point p) {
Rect b = targetNode.getBounds();
if (!b.isValid()) {
@@ -128,13 +114,9 @@ public class AndroidWidgetAbsoluteLayoutRule extends BaseLayout {
int x = p.x - b.x;
int y = p.y - b.y;
- def id_map = [:];
-
- // Need to remap ids if necessary
- if (isCopy || !sameCanvas) {
- collectIds(id_map, elements);
- id_map = remapIds(targetNode, id_map);
- }
+ // Collect IDs from dropped elements and remap them to new IDs
+ // if this is a copy or from a different canvas.
+ def id_map = getDropIdMap(targetNode, elements, feedback.isCopy || !feedback.sameCanvas);
targetNode.editXml("Add elements to AbsoluteLayout") {
@@ -149,8 +131,9 @@ public class AndroidWidgetAbsoluteLayoutRule extends BaseLayout {
INode newChild = targetNode.appendChild(fqcn);
// Copy all the attributes, modifying them as needed.
- addAttributes(newChild, element.getAttributes(), id_map) {
+ addAttributes(newChild, element, id_map) {
uri, name, value ->
+ // TODO exclude original parent attributes
if (name == "layout_x" || name == "layout_y") {
return false; // don't set these attributes
} else {
@@ -158,156 +141,25 @@ public class AndroidWidgetAbsoluteLayoutRule extends BaseLayout {
}
};
- targetNode.debugPrintf("POS: $offset , $x, $y, $be\n");
-
if (first) {
first = false;
if (be.isValid()) {
offset = new Point(x - be.x, y - be.y);
- targetNode.debugPrintf("FIRST OFFSET: $offset\n");
}
} else if (offset != null && be.isValid()) {
x = offset.x + be.x;
y = offset.y + be.y;
- targetNode.debugPrintf("NEXT X,Y: $x , $y");
} else {
x += 10;
y += be.isValid() ? be.h : 10;
- targetNode.debugPrintf("DEFAULT X,Y: $x , $y\n");
}
newChild.setAttribute(ANDROID_URI, "layout_x", "${x}dip");
newChild.setAttribute(ANDROID_URI, "layout_y", "${y}dip");
- def children = element.getInnerElements();
- addInnerElements(newChild, element.getInnerElements(), id_map);
- }
- }
- }
-
- void addAttributes(INode newNode, oldAttributes, id_map, Closure filter) {
- for (attr in oldAttributes) {
- String uri = attr.getUri();
- String name = attr.getName();
- String value = attr.getValue();
-
- def attrInfo = newNode.getAttributeInfo(uri, name);
- if (attrInfo != null) {
- def formats = attrInfo.getFormats();
- if (formats != null && IAttributeInfo.Format.REFERENCE in formats) {
- newNode.debugPrintf("REF attr: $name [$value]\n");
- if (id_map.containsKey(value)) {
- value = id_map[value][0];
- }
- }
- }
-
- if (filter != null) {
- value = filter(uri, name, value);
- }
- if (value != null && value != false && value != "") {
- newNode.setAttribute(uri, name, value);
+ addInnerElements(newChild, element, id_map);
}
}
}
- void addInnerElements(INode node, IDragElement[] elements, id_map) {
-
- elements.each { element ->
- String fqcn = element.getFqcn();
- INode newNode = node.appendChild(fqcn);
-
- addAttributes(newNode, element.getAttributes(), id_map, null /* closure */);
- addInnerElements(newNode, element.getInnerElements(), id_map);
- }
- }
-
- /**
- * Fills id_map with a map String id => tuple (String id, String fqcn)
- * where fqcn is the FQCN of the element (in case we want to generate
- * new IDs based on the element type.)
- */
- void collectIds(id_map, IDragElement[] elements) {
- elements.each { element ->
- def attr = element.getAttribute(ANDROID_URI, "id");
- if (attr != null) {
- String id = attr.getValue();
- if (id != null && id != "") {
- id_map.put(id, [id, element.getFqcn()]);
- }
- }
-
- collectIds(id_map, element.getInnerElements());
- }
- }
-
- Object remapIds(INode node, id_map) {
- // Visit the document to get a list of existing ids
- def existing_ids = [:];
- collectExistingIds(node.getRoot(), existing_ids);
-
- def new_map = [:];
- id_map.each() { key, value ->
- def id = normalizeId(key);
-
- if (!existing_ids.containsKey(id)) {
- // Not a conflict. Use as-is.
- new_map.put(key, value);
- if (key != id) {
- new_map.put(id, value);
- }
- } else {
- // There is a conflict. Get a new id.
- def new_id = findNewId(value[1], existing_ids);
- value[0] = new_id;
- new_map.put(id, value);
- new_map.put(id.replaceFirst("@\\+", "@"), value);
- }
- }
-
- return new_map;
- }
-
- String findNewId(String fqcn, existing_ids) {
- // Get the last component of the FQCN (e.g. "android.view.Button" => "Button")
- String name = fqcn[fqcn.lastIndexOf(".")+1 .. fqcn.length()-1];
-
- for (int i = 1; i < 1000000; i++) {
- String id = String.format("@+id/%s%02d", name, i);
- if (!existing_ids.containsKey(id)) {
- existing_ids.put(id, id);
- return id;
- }
- }
-
- // We'll never reach here.
- return null;
- }
-
- void collectExistingIds(INode root, existing_ids) {
- if (root == null) {
- return;
- }
-
- def id = root.getStringAttr(ANDROID_URI, "id");
- if (id != null) {
- id = normalizeId(id);
-
- if (!existing_ids.containsKey(id)) {
- existing_ids.put(id, id);
- }
- }
-
- root.getChildren().each {
- collectExistingIds(it, existing_ids);
- }
- }
-
- /** Transform @id/name into @+id/name to treat both forms the same way. */
- String normalizeId(String id) {
- if (id.indexOf("@+") == -1) {
- id = id.replaceFirst("@", "@+");
- }
- return id;
- }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy
index d723ac6..1f1b1c6 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy
@@ -21,20 +21,28 @@ package com.android.adt.gscripts;
*/
public class AndroidWidgetLinearLayoutRule extends BaseLayout {
+ public static String ATTR_ORIENTATION = "orientation";
+ public static String VALUE_VERTICAL = "vertical";
+
// ==== Drag'n'drop support ====
- DropFeedback onDropEnter(INode targetNode, String fqcn) {
+ DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) {
+
+ if (elements.length == 0) {
+ return null;
+ }
def bn = targetNode.getBounds();
if (!bn.isValid()) {
return;
}
- boolean isVertical = targetNode.getStringAttr("orientation") == "vertical";
+ boolean isVertical =
+ targetNode.getStringAttr(ANDROID_URI, ATTR_ORIENTATION) == VALUE_VERTICAL;
// Prepare a list of insertion points: X coords for horizontal, Y for vertical.
// Each list is a tuple: 0=pixel coordinate, 1=index of children or -1 for "at end".
- def indexes = [ ] ;
+ def indexes = [ ];
int last = isVertical ? bn.y : bn.x;
int pos = 0;
@@ -57,56 +65,97 @@ public class AndroidWidgetLinearLayoutRule extends BaseLayout {
return new DropFeedback(
[ "isVertical": isVertical, // boolean: True if vertical linear layout
"indexes": indexes, // list(tuple(0:int, 1:int)): insert points (pixels + index)
- "curr_x": null, // int: Current marker X position
- "curr_y": null, // int: Current marker Y position
- "insert_pos": -1 // int: Current drop insert index (-1 for "at the end")
+ "currX": null, // int: Current marker X position
+ "currY": null, // int: Current marker Y position
+ "insertPos": -1 // int: Current drop insert index (-1 for "at the end")
],
{
gc, node, feedback ->
// Paint closure for the LinearLayout.
+ // This is called by the canvas when a draw is needed.
+
+ drawFeedback(gc, node, elements, feedback);
+ });
+ }
+
+ void drawFeedback(IGraphics gc,
+ INode node,
+ IDragElement[] elements,
+ DropFeedback feedback) {
+ Rect b = node.getBounds();
+ if (!b.isValid()) {
+ return;
+ }
+
+ // Highlight the receiver
+ gc.setForeground(gc.registerColor(0x00FFFF00));
+ gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID);
+ gc.setLineWidth(2);
+ gc.drawRect(b);
+
+ gc.setLineStyle(IGraphics.LineStyle.LINE_DOT);
+ gc.setLineWidth(1);
+
+ def indexes = feedback.userData.indexes;
+ boolean isVertical = feedback.userData.isVertical;
- Rect b = node.getBounds();
- if (!b.isValid()) {
- return;
+ indexes.each {
+ int i = it[0];
+ if (isVertical) {
+ // draw horizontal lines
+ gc.drawLine(b.x, i, b.x + b.w, i);
+ } else {
+ // draw vertical lines
+ gc.drawLine(i, b.y, i, b.y + b.h);
}
+ }
+
+ def currX = feedback.userData.currX;
+ def currY = feedback.userData.currY;
- gc.setForeground(gc.registerColor(0x00FFFF00));
+ if (currX != null && currY != null) {
+ int x = currX;
+ int y = currY;
+ // Draw a mark at the drop point.
gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID);
gc.setLineWidth(2);
- gc.drawRect(b);
-
- gc.setLineStyle(IGraphics.LineStyle.LINE_DOT);
- gc.setLineWidth(1);
-
- indexes.each {
- int i = it[0];
- if (isVertical) {
- // draw horizontal lines
- gc.drawLine(b.x, i, b.x + b.w, i);
- } else {
- // draw vertical lines
- gc.drawLine(i, b.y, i, b.y + b.h);
- }
- }
- def curr_x = feedback.userData.curr_x;
- def curr_y = feedback.userData.curr_y;
+ gc.drawLine(x - 10, y - 10, x + 10, y + 10);
+ gc.drawLine(x + 10, y - 10, x - 10, y + 10);
+ gc.drawOval(x - 10, y - 10, x + 10, y + 10);
+
+ Rect be = elements[0].getBounds();
+
+ if (be.isValid()) {
+ // At least the first element has a bound. Draw rectangles
+ // for all dropped elements with valid bounds, offset at
+ // the drop point.
- if (curr_x != null && curr_y != null) {
- gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID);
- gc.setLineWidth(2);
+ int offsetX = x - be.x;
+ int offsetY = y - be.y;
- int x = curr_x;
- int y = curr_y;
- gc.drawLine(x - 10, y - 10, x + 10, y + 10);
- gc.drawLine(x + 10, y - 10, x - 10, y + 10);
- gc.drawRect(x - 10, y - 10, x + 10, y + 10);
+ // If there's a parent, keep the X/Y coordinate the same relative to the parent.
+ Rect pb = elements[0].getParentBounds();
+ if (pb.isValid()) {
+ if (isVertical) {
+ offsetX = b.x - pb.x;
+ } else {
+ offsetY = b.y - pb.y;
+ }
+ }
+
+ for (element in elements) {
+ drawElement(gc, element, offsetX, offsetY);
+ }
}
- })
+ }
}
- DropFeedback onDropMove(INode targetNode, String fqcn, DropFeedback feedback, Point p) {
+ DropFeedback onDropMove(INode targetNode,
+ IDragElement[] elements,
+ DropFeedback feedback,
+ Point p) {
def data = feedback.userData;
Rect b = targetNode.getBounds();
@@ -134,40 +183,71 @@ public class AndroidWidgetLinearLayoutRule extends BaseLayout {
}
if (bestIndex != Integer.MIN_VALUE) {
- def old_x = data.curr_x;
- def old_y = data.curr_y;
+ def old_x = data.currX;
+ def old_y = data.currY;
if (isVertical) {
- data.curr_x = b.x + b.w / 2;
- data.curr_y = bestIndex;
+ data.currX = b.x + b.w / 2;
+ data.currY = bestIndex;
} else {
- data.curr_x = bestIndex;
- data.curr_y = b.y + b.h / 2;
+ data.currX = bestIndex;
+ data.currY = b.y + b.h / 2;
}
- data.insert_pos = bestPos;
+ data.insertPos = bestPos;
- feedback.requestPaint = (old_x != data.curr_x) || (old_y != data.curr_y);
+ feedback.requestPaint = (old_x != data.currX) || (old_y != data.currY);
}
return feedback;
}
- void onDropLeave(INode targetNode, String fqcn, DropFeedback feedback) {
+ void onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback) {
// ignore
}
- void onDropped(INode targetNode, String fqcn, DropFeedback feedback, Point p) {
- int insert_pos = feedback.userData.insert_pos;
+ void onDropped(INode targetNode,
+ IDragElement[] elements,
+ DropFeedback feedback,
+ Point p) {
- targetNode.debugPrintf("Linear.drop: add ${fqcn} at position ${insert_pos}");
+ int insertPos = feedback.userData.insertPos;
- // Get the last component of the FQCN (e.g. "android.view.Button" => "Button")
- String name = fqcn;
- name = name[name.lastIndexOf(".")+1 .. name.length()-1];
+ // Collect IDs from dropped elements and remap them to new IDs
+ // if this is a copy or from a different canvas.
+ def idMap = getDropIdMap(targetNode, elements, feedback.isCopy || !feedback.sameCanvas);
- targetNode.editXml("Add ${name} to LinearLayout") {
- INode e = targetNode.insertChildAt(fqcn, insert_pos);
+ targetNode.editXml("Add elements to LinearLayout") {
+
+ // Now write the new elements.
+ for (element in elements) {
+ String fqcn = element.getFqcn();
+ Rect be = element.getBounds();
+
+ INode newChild = targetNode.insertChildAt(fqcn, insertPos);
+
+ // insertPos==-1 means to insert at the end. Otherwise
+ // increment the insertion position.
+ if (insertPos >= 0) {
+ insertPos++;
+ }
+
+ // Copy all the attributes, modifying them as needed.
+ def attrFilter = getLayoutAttrFilter();
+ addAttributes(newChild, element, idMap) {
+ uri, name, value ->
+ // TODO need a better way to exclude other layout attributes dynamically
+ if (uri == ANDROID_URI && name in attrFilter) {
+ return false; // don't set these attributes
+ } else {
+ return value;
+ }
+ };
+
+ addInnerElements(newChild, element, idMap);
+ }
}
+
+
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy
index 6325dfa..b7c00ca 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy
@@ -30,8 +30,8 @@ public class AndroidWidgetListViewRule extends BaseView {
* However ListView is special in that ideally we want fill_parent width by default.
*/
public Map<?, ?> getDefaultAttributes() {
- // TODO: find a way to plug in the new value match_parent.
- return [ "layout_width" : "fill_parent" ];
+ // TODO: find a way to plug in the new value VALUE_MATCH_PARENT.
+ return [ ATTR_LAYOUT_WIDTH : VALUE_FILL_PARENT ];
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.RelativeLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.RelativeLayout.groovy
index 643047f..02606d9 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.RelativeLayout.groovy
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.RelativeLayout.groovy
@@ -49,7 +49,7 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
def infos = [];
def addAttr = {
- def a = childNode.getStringAttr("layout_${it}");
+ def a = childNode.getStringAttr(ANDROID_URI, "layout_${it}");
if (a) {
infos += "${it}: ${a}";
}
@@ -88,28 +88,40 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
// ==== Drag'n'drop support ====
- DropFeedback onDropEnter(INode targetNode, String fqcn) {
+ DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) {
+
+ if (elements.length == 0) {
+ return null;
+ }
def bn = targetNode.getBounds();
if (!bn.isValid()) {
return;
}
+ // Collect the ids of the elements being dragged
+ def movedIds = collectIds([:], elements).keySet().asList();
+
// Prepare the drop feedback
return new DropFeedback(
[ "child": null, // INode: Current child under cursor
"index": 0, // int: Index of child in the parent children list
"zones": null, // Valid "anchor" zones for the current child
// of type list(map(rect:Rect, attr:[String]))
- "curr": null, // map: Current zone
+ "curr": null, // map: Current zone
+ "movedIds": movedIds,
+ "cachedLinkIds": [:]
],
{ gc, node, feedback ->
// Paint closure for the RelativeLayout just defers to the method below
- drawRelativeDropFeedback(gc, node, feedback);
+ drawRelativeDropFeedback(gc, node, elements, feedback);
});
}
- DropFeedback onDropMove(INode layoutNode, String fqcn, DropFeedback feedback, Point p) {
+ DropFeedback onDropMove(INode targetNode,
+ IDragElement[] elements,
+ DropFeedback feedback,
+ Point p) {
def data = feedback.userData;
Rect area = feedback.captureArea;
@@ -123,9 +135,35 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
// Find the current direct children under the cursor
def childNode = null;
def childIndex = -1;
- for(child in layoutNode.getChildren()) {
+ nextChild: for(child in targetNode.getChildren()) {
childIndex++;
- if (child.getBounds().contains(p)) {
+ def bc = child.getBounds();
+ if (bc.contains(p)) {
+
+ // TODO visually indicate this target node has been rejected,
+ // e.g. by drawing a semi-transp rect on it or drawing a red cross at
+ // the cursor point.
+
+ // If we're doing a move operation within the same canvas, we can't
+ // attach the moved object to one belonging to the selection since
+ // it will disappear after the move.
+ if (feedback.sameCanvas && !feedback.isCopy) {
+ for (element in elements) {
+ if (bc == element.getBounds()) {
+ continue nextChild;
+ }
+ }
+ }
+
+ // One more limitation: if we're moving one or more elements, we can't
+ // drop them on a child which relative position is expressed directly or
+ // indirectly based on the element being moved.
+ if (!feedback.isCopy) {
+ if (searchRelativeIds(child, data.movedIds, data.cachedLinkIds)) {
+ continue nextChild;
+ }
+ }
+
childNode = child;
break;
}
@@ -151,7 +189,7 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
data.index = -1;
data.curr = null;
- def zone = computeBorderDropZone(layoutNode.getBounds(), p);
+ def zone = computeBorderDropZone(targetNode.getBounds(), p);
if (zone == null) {
data.zones = null;
@@ -183,6 +221,71 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
return feedback;
}
+ /**
+ * Returns true if the child has any attribute of Format.REFERENCE which
+ * value matches one of the ids in movedIds.
+ */
+ def searchRelativeIds(INode node, List movedIds, Map cachedLinkIds) {
+ def ids = getLinkedIds(node, cachedLinkIds);
+
+ for (id in ids) {
+ if (id in movedIds) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ def getLinkedIds(INode node, Map cachedLinkIds) {
+ def ids = cachedLinkIds[node];
+
+ if (ids != null) {
+ return ids;
+ }
+
+ // we don't have cached data on this child, so create a list of
+ // all the linked id it is referencing.
+ ids = [];
+ cachedLinkIds[node] = ids;
+ for (attr in node.getAttributes()) {
+ def attrInfo = node.getAttributeInfo(attr.getUri(), attr.getName());
+ if (attrInfo == null) {
+ continue;
+ }
+ def formats = attrInfo.getFormats();
+ if (formats == null || !(IAttributeInfo.Format.REFERENCE in formats)) {
+ continue;
+ }
+ def id = attr.getValue();
+ id = normalizeId(id);
+ if (id in ids) {
+ continue;
+ }
+ ids.add(id);
+
+ // Find the sibling with that id
+ def p = node.getParent();
+ if (p == null) {
+ continue;
+ }
+ for (child in p.getChildren()) {
+ if (child == node) {
+ continue;
+ }
+ def childId = child.getStringAttr(ANDROID_URI, ATTR_ID);
+ childId = normalizeId(childId);
+ if (id == childId) {
+ def linkedIds = getLinkedIds(child, cachedLinkIds);
+ ids.addAll(linkedIds);
+ break;
+ }
+ }
+ }
+
+ return ids;
+ }
+
def computeBorderDropZone(Rect bounds, Point p) {
int x = p.x;
@@ -320,8 +423,11 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
return [ bounds, zones ];
}
- void drawRelativeDropFeedback(IGraphics gc, INode layoutNode, DropFeedback feedback) {
- Rect b = layoutNode.getBounds();
+ void drawRelativeDropFeedback(IGraphics gc,
+ INode targetNode,
+ IDragElement[] elements,
+ DropFeedback feedback) {
+ Rect b = targetNode.getBounds();
if (!b.isValid()) {
return;
}
@@ -356,7 +462,7 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
int h = gc.getFontHeight();
String id = null;
if (data.child) {
- id = data.child.getStringAttr("id");
+ id = data.child.getStringAttr(ANDROID_URI, ATTR_ID);
}
data.curr.attr.each {
String s = it;
@@ -376,41 +482,87 @@ public class AndroidWidgetRelativeLayoutRule extends BaseLayout {
y = mark.y;
gc.drawLine(x - 10, y - 10, x + 10, y + 10);
gc.drawLine(x + 10, y - 10, x - 10, y + 10);
- gc.drawRect(x - 10, y - 10, x + 10, y + 10);
- }
+ gc.drawOval(x - 10, y - 10, x + 10, y + 10);
+
+ Rect be = elements[0].getBounds();
+
+ if (be.isValid()) {
+ // At least the first element has a bound. Draw rectangles
+ // for all dropped elements with valid bounds, offset at
+ // the drop point.
+
+ int offsetX = x - be.x;
+ int offsetY = y - be.y;
+
+ gc.setForeground(gc.registerColor(0x00FFFF00));
+ for (element in elements) {
+ drawElement(gc, element, offsetX, offsetY);
+ }
+ }
+ }
}
}
- void onDropLeave(INode targetNode, String fqcn, DropFeedback feedback) {
+ void onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback) {
// Free the last captured rect, if any
feedback.captureArea = null;
}
- void onDropped(INode targetNode, String fqcn, DropFeedback feedback, Point p) {
+ void onDropped(INode targetNode,
+ IDragElement[] elements,
+ DropFeedback feedback,
+ Point p) {
def data = feedback.userData;
if (!data.curr) {
return;
}
def index = data.index;
+ def insertPos = data.insertPos;
- targetNode.debugPrintf("Relative.drop: add ${fqcn} after index ${index}");
+ // Collect IDs from dropped elements and remap them to new IDs
+ // if this is a copy or from a different canvas.
+ def idMap = getDropIdMap(targetNode, elements, feedback.isCopy || !feedback.sameCanvas);
- // Get the last component of the FQCN (e.g. "android.view.Button" => "Button")
- String name = fqcn;
- name = name[name.lastIndexOf(".")+1 .. name.length()-1];
+ targetNode.editXml("Add elements to RelativeLayout") {
- targetNode.editXml("Add ${name} to RelativeLayout") {
- INode e = targetNode.insertChildAt(fqcn, index + 1);
+ // Now write the new elements.
+ for (element in elements) {
+ String fqcn = element.getFqcn();
+ Rect be = element.getBounds();
- String id = null;
- if (data.child) {
- id = data.child.getStringAttr("id");
- }
+ // index==-1 means to insert at the end.
+ // Otherwise increment the insertion position.
+ if (index >= 0) {
+ index++;
+ }
- data.curr.attr.each {
- e.setAttribute("layout_${it}", id ? id : "true");
+ INode newChild = targetNode.insertChildAt(fqcn, index);
+
+ // Copy all the attributes, modifying them as needed.
+ def attrFilter = getLayoutAttrFilter();
+ addAttributes(newChild, element, idMap) {
+ uri, name, value ->
+ // TODO need a better way to exclude other layout attributes dynamically
+ if (uri == ANDROID_URI && name in attrFilter) {
+ return false; // don't set these attributes
+ } else {
+ return value;
+ }
+ };
+
+// TODO... seems totally wrong. REVISIT or EXPLAIN
+ String id = null;
+ if (data.child) {
+ id = data.child.getStringAttr(ANDROID_URI, ATTR_ID);
+ }
+
+ data.curr.attr.each {
+ newChild.setAttribute(ANDROID_URI, "layout_${it}", id ? id : "true");
+ }
+
+ addInnerElements(newChild, element, idMap);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropFeedback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropFeedback.java
index 315caf4..82a07a9 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropFeedback.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropFeedback.java
@@ -52,6 +52,19 @@ public class DropFeedback {
public Rect captureArea;
/**
+ * Set to true by the drag'n'drop engine when the current drag operation is a copy.
+ * When false the operation is a move and <em>after</em> a successful drop the source
+ * elements will be deleted.
+ */
+ public boolean isCopy;
+
+ /**
+ * Set to true when the drag'n'drop starts and ends in the same canvas of the
+ * same Eclipse instance.
+ */
+ public boolean sameCanvas;
+
+ /**
* Initializes the drop feedback with the given user data and paint closure.
* A paint is requested if the paint closure is non-null.
*/
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IDragElement.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IDragElement.java
index b9be937..2a91401 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IDragElement.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IDragElement.java
@@ -35,6 +35,8 @@ public interface IDragElement {
* Returns the bounds of the element's node, if it originated from an existing
* canvas. The rectangle is invalid and non-null when the element originated
* from the object palette.
+ *
+ * The bounds are absolute for the canvas.
*/
public abstract Rect getBounds();
@@ -46,6 +48,14 @@ public interface IDragElement {
public abstract String getParentFqcn();
/**
+ * Returns the bounds of the element's parent, absolute for the canvas, or invalid if there
+ * is no suitable parent. This is generally invalid when {@link #getParentFqcn()} is null.
+ *
+ * The returned rectangle can be invalid. It is never null.
+ */
+ public abstract Rect getParentBounds();
+
+ /**
* Returns a list of attributes. The list can be empty but is never null.
*/
public abstract IDragAttribute[] getAttributes();
@@ -62,10 +72,18 @@ public interface IDragElement {
/**
* An XML attribute in the {@link IDragElement}.
+ * <p/>
+ * The attribute is always represented by a namespace URI, a name and a value.
+ * The name cannot be empty.
+ * The namespace URI can be empty for an attribute without a namespace but is never null.
+ * The value can be empty but cannot be null.
*/
public interface IDragAttribute {
- /** Returns the namespace URI of the attribute. Cannot be null nor empty. */
+ /**
+ * Returns the namespace URI of the attribute.
+ * Can be empty for an attribute without a namespace but is never null.
+ */
public abstract String getUri();
/** Returns the XML local name of the attribute. Cannot be null nor empty. */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java
index acd9327..b850d60 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java
@@ -17,6 +17,8 @@
package com.android.ide.eclipse.adt.editors.layout.gscripts;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.IDragElement.IDragAttribute;
+
import groovy.lang.Closure;
@@ -168,10 +170,35 @@ public interface INode {
*/
public IAttributeInfo getAttributeInfo(String uri, String attrName);
+
+ /**
+ * Returns the list of all attributes defined in the XML for this node.
+ * <p/>
+ * This looks up an attribute in the <em>current</em> XML source, not the in-memory model.
+ * That means that if called in the context of {@link #editXml(String, Closure)}, the value
+ * returned here is not affected by {@link #setAttribute(String, String, String)} until
+ * the editXml closure is completed and the actual XML is updated.
+ *
+ * @return A non-null possible-empty list of {@link IAttribute}.
+ */
+ public IAttribute[] getAttributes();
+
// -----------
/** TODO: this is a hack. Shouldn't be here but instead part of some kind of helper
* given to IViewRule implementations.
*/
void debugPrintf(String msg, Object...params);
+
+ // -----------
+
+ /**
+ * An XML attribute in an {@link INode}.
+ * <p/>
+ * The attribute is always represented by a namespace URI, a name and a value.
+ * The name cannot be empty.
+ * The namespace URI can be empty for an attribute without a namespace but is never null.
+ * The value can be empty but cannot be null.
+ */
+ public static interface IAttribute extends IDragAttribute { }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java
index 9a3bce3..6200d8a 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java
@@ -170,8 +170,6 @@ public interface IViewRule {
void onDropped(INode targetNode,
IDragElement[] elements,
DropFeedback feedback,
- Point where,
- boolean isCopy,
- boolean sameCanvas);
+ Point where);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java
index f98c4eb..a238d14 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java
@@ -125,6 +125,15 @@ public class Rect {
public boolean equals(Object obj) {
if (obj instanceof Rect) {
Rect rhs = (Rect) obj;
+ // validity must be equal on both sides.
+ if (isValid() != rhs.isValid()) {
+ return false;
+ }
+ // an invalid rect is equal to any other invalid rect regardless of coordinates
+ if (!isValid() && !rhs.isValid()) {
+ return true;
+ }
+
return this.x == rhs.x && this.y == rhs.y && this.w == rhs.w && this.h == rhs.h;
} else if (obj instanceof Rectangle) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
index 29a148d..ee0b802 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
@@ -209,6 +209,8 @@ import java.util.Arrays;
public void dropAccept(DropTargetEvent event) {
AdtPlugin.printErrorToConsole("DEBUG", "drop accept");
+ checkDataType(event);
+
// If we have a valid target node and it matches the one we saved in
// dragLeave then we restore the DropFeedback that we saved in dragLeave.
if (mLeaveTargetNode != null) {
@@ -216,17 +218,14 @@ import java.util.Arrays;
mFeedback = mLeaveFeedback;
mCurrentView = mLeaveView;
}
- mLeaveTargetNode = null;
- mLeaveFeedback = null;
- mLeaveView = null;
-
- checkDataType(event);
- if (event.detail != DND.DROP_NONE) {
- processDropEvent(event);
- } else {
+ if (mLeaveTargetNode == null || event.detail == DND.DROP_NONE) {
clearDropInfo();
}
+
+ mLeaveTargetNode = null;
+ mLeaveFeedback = null;
+ mLeaveView = null;
}
/*
@@ -263,21 +262,32 @@ import java.util.Arrays;
Point where = mCanvas.displayToCanvasPoint(event.x, event.y);
- boolean isCopy = event.detail == DND.DROP_COPY;
- boolean sameCanvas = mCanvas == mGlobalDragInfo.getSourceCanvas();
-
+ updateDropFeedback(mFeedback, event);
mCanvas.getRulesEngine().callOnDropped(mTargetNode,
elements,
mFeedback,
- where,
- isCopy,
- sameCanvas);
+ where);
clearDropInfo();
mCanvas.redraw();
}
/**
+ * Updates the {@link DropFeedback#isCopy} and {@link DropFeedback#sameCanvas} fields
+ * of the given {@link DropFeedback}. This is generally called right before invoking
+ * one of the callOnXyz methods of GRE to refresh the fields.
+ *
+ * @param df The current {@link DropFeedback}.
+ * @param event An optional event to determine if the current operaiton is copy or move.
+ */
+ private void updateDropFeedback(DropFeedback df, DropTargetEvent event) {
+ if (event != null) {
+ df.isCopy = event.detail == DND.DROP_COPY;
+ }
+ df.sameCanvas = mCanvas == mGlobalDragInfo.getSourceCanvas();
+ }
+
+ /**
* Invoked by the canvas to refresh the display.
* @param gCWrapper The GC wrapper, never null.
*/
@@ -371,37 +381,46 @@ import java.util.Arrays;
} else {
// vi is a new current view.
- // Query GRE for onDropEnter on the view till we find one that returns a non-null
- // object.
+ // Query GRE for onDropEnter on the ViewInfo hierarchy, starting from the child
+ // towards its parent, till we find one that returns a non-null drop feedback.
DropFeedback df = null;
NodeProxy targetNode = null;
- boolean invalidTarget = false;
-
- if (mCanvas == mGlobalDragInfo.getSourceCanvas()) {
- // If we're drag'n'drop in the same canvas, none of the objects being
- // dragged should be considered as valid targets.
- CanvasSelection[] selection = mGlobalDragInfo.getCurrentSelection();
- if (selection != null) {
- for (CanvasSelection cs : selection) {
- if (cs.getViewInfo() == vi) {
- invalidTarget = true;
- break;
+
+ for (CanvasViewInfo targetVi = vi;
+ targetVi != null && df == null;
+ targetVi = targetVi.getParent()) {
+ targetNode = mCanvas.getNodeFactory().create(targetVi);
+ df = mCanvas.getRulesEngine().callOnDropEnter(targetNode,
+ mCurrentDragElements);
+
+ if (df != null &&
+ event.detail == DND.DROP_MOVE &&
+ mCanvas == mGlobalDragInfo.getSourceCanvas()) {
+ // You can't move an object into itself in the same canvas.
+ // E.g. case of moving a layout and the node under the mouse is the
+ // layout itself: a copy would be ok but not a move operation of the
+ // layout into himself.
+
+ CanvasSelection[] selection = mGlobalDragInfo.getCurrentSelection();
+ if (selection != null) {
+ for (CanvasSelection cs : selection) {
+ if (cs.getViewInfo() == targetVi) {
+ // The node that responded is one of the selection roots.
+ // Simply invalidate the drop feedback and move on the
+ // parent in the ViewInfo chain.
+
+ updateDropFeedback(df, event);
+ mCanvas.getRulesEngine().callOnDropLeave(
+ targetNode, mCurrentDragElements, df);
+ df = null;
+ targetNode = null;
+ }
}
}
}
}
- if (!invalidTarget) {
- for (CanvasViewInfo targetVi = vi;
- targetVi != null && df == null;
- targetVi = targetVi.getParent()) {
- targetNode = mCanvas.getNodeFactory().create(targetVi);
- df = mCanvas.getRulesEngine().callOnDropEnter(targetNode,
- mCurrentDragElements);
- }
- }
-
if (df != null && targetNode != mTargetNode) {
// We found a new target node for the drag'n'drop.
// Release the previous one, if any.
@@ -435,11 +454,14 @@ import java.util.Arrays;
// this is a move inside the same view
com.android.ide.eclipse.adt.editors.layout.gscripts.Point p2 =
new com.android.ide.eclipse.adt.editors.layout.gscripts.Point(x, y);
+ updateDropFeedback(mFeedback, event);
DropFeedback df = mCanvas.getRulesEngine().callOnDropMove(
mTargetNode, mCurrentDragElements, mFeedback, p2);
if (df == null) {
// The target is no longer interested in the drop move.
callDropLeave();
+ } else if (df != mFeedback) {
+ mFeedback = df;
}
}
@@ -454,6 +476,7 @@ import java.util.Arrays;
*/
private void callDropLeave() {
if (mTargetNode != null && mFeedback != null) {
+ updateDropFeedback(mFeedback, null);
mCanvas.getRulesEngine().callOnDropLeave(mTargetNode, mCurrentDragElements, mFeedback);
}
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 d043664..fb5ee04 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
@@ -1218,13 +1218,17 @@ import java.util.ListIterator;
String fqcn = SimpleXmlTransfer.getFqcn(uiNode.getDescriptor());
String parentFqcn = null;
Rect bounds = new Rect(vi.getAbsRect());
+ Rect parentBounds = null;
UiElementNode uiParent = uiNode.getUiParent();
if (uiParent != null) {
parentFqcn = SimpleXmlTransfer.getFqcn(uiParent.getDescriptor());
}
+ if (vi.getParent() != null) {
+ parentBounds = new Rect(vi.getParent().getAbsRect());
+ }
- SimpleElement e = new SimpleElement(fqcn, parentFqcn, bounds);
+ SimpleElement e = new SimpleElement(fqcn, parentFqcn, bounds, parentBounds);
for (UiAttributeNode attr : uiNode.getUiAttributes()) {
String value = attr.getCurrentValue();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
index 4073d8a..0e16105 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
@@ -431,7 +431,10 @@ public class PaletteComposite extends Composite {
public DescDragSourceListener(ElementDescriptor desc) {
SimpleElement se = new SimpleElement(
- SimpleXmlTransfer.getFqcn(desc), null /* parentFqcn */, null /* bounds */);
+ SimpleXmlTransfer.getFqcn(desc),
+ null /* parentFqcn */,
+ null /* bounds */,
+ null /* parentBounds */);
mElements = new SimpleElement[] { se };
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java
index d9edf30..8dc81a6 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.eclipse.adt.editors.layout.gscripts.IDragElement.IDragAttribute;
+import com.android.ide.eclipse.adt.editors.layout.gscripts.INode.IAttribute;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -24,31 +25,39 @@ import java.util.regex.Pattern;
/**
* Represents one XML attribute in a {@link SimpleElement}.
* <p/>
- * The attribute is always represented by a namespace URI, a name and a value, none of which
- * can be null. The name and URI cannot be empty.
+ * The attribute is always represented by a namespace URI, a name and a value.
+ * The name cannot be empty.
+ * The namespace URI can be empty for an attribute without a namespace but is never null.
+ * The value can be empty but cannot be null.
* <p/>
* For a more detailed explanation of the purpose of this class,
* please see {@link SimpleXmlTransfer}.
*/
-public class SimpleAttribute implements IDragAttribute {
+public class SimpleAttribute implements IDragAttribute, IAttribute {
private final String mName;
private final String mValue;
private final String mUri;
/**
* Creates a new {@link SimpleAttribute}.
+ * <p/>
+ * Any null value will be converted to an empty non-null string.
+ * However it is a semantic error to use an empty name -- no assertion is done though.
*
- * @param uri The URI of the attribute. Cannot be null nor empty.
- * @param name The XML local name of the attribute. Cannot be null nor empty.
- * @param value The value of the attribute. Can be empty but not null.
+ * @param uri The URI of the attribute.
+ * @param name The XML local name of the attribute.
+ * @param value The value of the attribute.
*/
public SimpleAttribute(String uri, String name, String value) {
- mUri = uri;
- mName = name;
- mValue = value;
+ mUri = uri == null ? "" : uri;
+ mName = name == null ? "" : name;
+ mValue = value == null ? "" : value;
}
- /** Returns the namespace URI of the attribute. Cannot be null nor empty. */
+ /**
+ * Returns the namespace URI of the attribute.
+ * Can be empty for an attribute without a namespace but is never null.
+ */
public String getUri() {
return mUri;
}
@@ -67,11 +76,14 @@ public class SimpleAttribute implements IDragAttribute {
@Override
public String toString() {
- return String.format("@%s:%s=%s\n", mName, mUri, mValue); //$NON-NLS-1$
+ return String.format("@%s:%s=%s\n", //$NON-NLS-1$
+ mName,
+ mUri,
+ mValue);
}
private static final Pattern REGEXP =
- Pattern.compile("[^@]*@([^:]+):([^=]+)=([^\n]*)\n*"); //$NON-NLS-1$
+ Pattern.compile("[^@]*@([^:]+):([^=]*)=([^\n]*)\n*"); //$NON-NLS-1$
static SimpleAttribute parseString(String value) {
Matcher m = REGEXP.matcher(value);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java
index 8103973..fd06726 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java
@@ -33,11 +33,12 @@ import java.util.ArrayList;
public class SimpleElement implements IDragElement {
/** Version number of the internal serialized string format. */
- private static final String FORMAT_VERSION = "2";
+ private static final String FORMAT_VERSION = "3";
private final String mFqcn;
private final String mParentFqcn;
private final Rect mBounds;
+ private final Rect mParentBounds;
private final ArrayList<IDragAttribute> mAttributes = new ArrayList<IDragAttribute>();
private final ArrayList<IDragElement> mElements = new ArrayList<IDragElement>();
@@ -53,11 +54,13 @@ public class SimpleElement implements IDragElement {
* Can be null but not empty.
* @param bounds The canvas bounds of the originating canvas node of the element.
* If null, a non-null invalid rectangle will be assigned.
+ * @param parentBounds The canvas bounds of the parent of this element. Can be null.
*/
- public SimpleElement(String fqcn, String parentFqcn, Rect bounds) {
+ public SimpleElement(String fqcn, String parentFqcn, Rect bounds, Rect parentBounds) {
mFqcn = fqcn;
mParentFqcn = parentFqcn;
mBounds = bounds == null ? new Rect() : bounds.copy();
+ mParentBounds = parentBounds == null ? new Rect() : parentBounds.copy();
}
/**
@@ -86,6 +89,14 @@ public class SimpleElement implements IDragElement {
return mParentFqcn;
}
+ /**
+ * Returns the bounds of the element's parent, absolute for the canvas, or null if there
+ * is no suitable parent. This is null when {@link #getParentFqcn()} is null.
+ */
+ public Rect getParentBounds() {
+ return mParentBounds;
+ }
+
public IDragAttribute[] getAttributes() {
if (mCachedAttributes == null) {
mCachedAttributes = mAttributes.toArray(new IDragAttribute[mAttributes.size()]);
@@ -132,7 +143,11 @@ public class SimpleElement implements IDragElement {
}
if (mBounds != null && mBounds.isValid()) {
sb.append(String.format(",R=%d %d %d %d", mBounds.x, mBounds.y, mBounds.w, mBounds.h));
- }
+ }
+ if (mParentBounds != null && mParentBounds.isValid()) {
+ sb.append(String.format(",Q=%d %d %d %d",
+ mParentBounds.x, mParentBounds.y, mParentBounds.w, mParentBounds.h));
+ }
sb.append('\n');
for (IDragAttribute a : mAttributes) {
sb.append(a.toString());
@@ -174,6 +189,7 @@ public class SimpleElement implements IDragElement {
String fqcn = null;
String parent = null;
Rect bounds = null;
+ Rect pbounds = null;
for (String s2 : s.substring(1).split(",")) { //$NON-NLS-1$
int pos = s2.indexOf('=');
@@ -198,18 +214,24 @@ public class SimpleElement implements IDragElement {
} else if (key.equals("P")) { //$NON-NLS-1$
parent = value;
- } else if (key.equals("R")) { //$NON-NLS-1$
+ } else if (key.equals("R") || key.equals("Q")) { //$NON-NLS-1$ //$NON-NLS-2$
// Parse the canvas bounds
String[] sb = value.split(" +"); //$NON-NLS-1$
if (sb != null && sb.length == 4) {
+ Rect r = null;
try {
- bounds = new Rect();
- bounds.x = Integer.parseInt(sb[0]);
- bounds.y = Integer.parseInt(sb[1]);
- bounds.w = Integer.parseInt(sb[2]);
- bounds.h = Integer.parseInt(sb[3]);
+ r = new Rect();
+ r.x = Integer.parseInt(sb[0]);
+ r.y = Integer.parseInt(sb[1]);
+ r.w = Integer.parseInt(sb[2]);
+ r.h = Integer.parseInt(sb[3]);
+
+ if (key.equals("R")) {
+ bounds = r;
+ } else {
+ pbounds = r;
+ }
} catch (NumberFormatException ignore) {
- bounds = null;
}
}
}
@@ -217,7 +239,7 @@ public class SimpleElement implements IDragElement {
// We need at least a valid name to recreate an element
if (version != null && fqcn != null && fqcn.length() > 0) {
- e = new SimpleElement(fqcn, parent, bounds);
+ e = new SimpleElement(fqcn, parent, bounds, pbounds);
}
} else {
// This is an inner element... need to parse the { line again.
@@ -259,6 +281,10 @@ public class SimpleElement implements IDragElement {
(mParentFqcn != null && !mParentFqcn.equals(se.mParentFqcn))) {
return false;
}
+ if ((mParentBounds == null && se.mParentBounds != null) ||
+ (mParentBounds != null && !mParentBounds.equals(se.mParentBounds))) {
+ return false;
+ }
return mFqcn.equals(se.mFqcn) &&
mAttributes.size() == se.mAttributes.size() &&
@@ -278,9 +304,12 @@ public class SimpleElement implements IDragElement {
if (mParentFqcn != null) {
c = 31*c + mParentFqcn.hashCode();
}
- if (mBounds != null) {
+ if (mBounds != null && mBounds.isValid()) {
c = 31*c + mBounds.hashCode();
}
+ if (mParentBounds != null && mParentBounds.isValid()) {
+ c = 31*c + mParentBounds.hashCode();
+ }
if (c > 0x0FFFFFFFFL) {
// wrap any overflow
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
index 5f1b889..f95e44d 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
@@ -28,6 +28,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescripto
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleAttribute;
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.UiDocumentNode;
@@ -317,6 +318,33 @@ public class NodeProxy implements INode {
return null;
}
+ public IAttribute[] getAttributes() {
+ UiElementNode uiNode = mNode;
+
+ if (uiNode.getXmlNode() != null) {
+ Node xmlNode = uiNode.getXmlNode();
+ if (xmlNode != null) {
+ NamedNodeMap nodeAttributes = xmlNode.getAttributes();
+ if (nodeAttributes != null) {
+
+ int n = nodeAttributes.getLength();
+ IAttribute[] result = new IAttribute[n];
+ for (int i = 0; i < n; i++) {
+ Node attr = nodeAttributes.item(i);
+ String uri = attr.getNamespaceURI();
+ String name = attr.getLocalName();
+ String value = attr.getNodeValue();
+
+ result[i] = new SimpleAttribute(uri, name, value);
+ }
+ return result;
+ }
+ }
+ }
+ return null;
+
+ }
+
// --- internal helpers ---
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
index 01e2515..6dd2a79 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
@@ -294,15 +294,13 @@ public class RulesEngine {
public void callOnDropped(NodeProxy targetNode,
IDragElement[] elements,
DropFeedback feedback,
- Point where,
- boolean isCopy,
- boolean sameCanvas) {
+ Point where) {
// try to find a rule for this element's FQCN
IViewRule rule = loadRule(targetNode.getNode());
if (rule != null) {
try {
- rule.onDropped(targetNode, elements, feedback, where, isCopy, sameCanvas);
+ rule.onDropped(targetNode, elements, feedback, where);
} catch (Exception e) {
logError("%s.onDropped() failed: %s",
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElementTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElementTest.java
index d97a230..7c098bd 100755
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElementTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElementTest.java
@@ -58,7 +58,8 @@ public class SimpleElementTest extends TestCase {
e = new SimpleElement("android.view.LinearLayout", // fqcn
"android.view.FrameLayout", // parentFqcn
- new Rect(10, 5, 60, 40));
+ new Rect(10, 5, 60, 40), // bounds
+ new Rect(0, 0, 320, 480)); // parentBounds
}
public final void testGetFqcn() {
@@ -73,15 +74,19 @@ public class SimpleElementTest extends TestCase {
assertEquals(new Rect(10, 5, 60, 40), e.getBounds());
}
+ public final void testGetParentBounds() {
+ assertEquals(new Rect(0, 0, 320, 480), e.getParentBounds());
+ }
+
public final void testToString() {
- assertEquals("{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ assertEquals("{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"}\n",
e.toString());
e.addAttribute(new SimpleAttribute("uri", "name", "value"));
e.addAttribute(new SimpleAttribute("my-uri", "second-name", "my = value "));
- assertEquals("{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ assertEquals("{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"@name:uri=value\n" +
"@second-name:my-uri=my = value \n" +
"}\n",
@@ -89,23 +94,25 @@ public class SimpleElementTest extends TestCase {
SimpleElement e2 = new SimpleElement("android.view.Button",
"android.view.LinearLayout",
- new Rect(10, 20, 30, 40));
+ new Rect(10, 20, 30, 40),
+ new Rect(0, 0, 320, 480));
e2.addAttribute(new SimpleAttribute("uri1", "name1", "value1"));
SimpleElement e3 = new SimpleElement("android.view.CheckBox",
"android.view.LinearLayout",
- new Rect(-1, -2, -3, -4)); // invalid rect is ignored
+ new Rect(-1, -2, -3, -4), // invalid rect is ignored
+ new Rect(-1, -2, -3, -4)); // invalid rectis ignored
e3.addAttribute(new SimpleAttribute("uri2", "name2", "value2"));
e3.addAttribute(new SimpleAttribute("uri3", "name3", "value3"));
e.addInnerElement(e2);
e.addInnerElement(e3);
- assertEquals("{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ assertEquals("{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"@name:uri=value\n" +
"@second-name:my-uri=my = value \n" +
- "{V=2,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40\n" +
+ "{V=3,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40,Q=0 0 320 480\n" +
"@name1:uri1=value1\n" +
"}\n" +
- "{V=2,N=android.view.CheckBox,P=android.view.LinearLayout\n" +
+ "{V=3,N=android.view.CheckBox,P=android.view.LinearLayout\n" +
"@name2:uri2=value2\n" +
"@name3:uri3=value3\n" +
"}\n" +
@@ -115,32 +122,34 @@ public class SimpleElementTest extends TestCase {
public final void testParseString() {
assertArrayEquals(
- new SimpleElement[] { new SimpleElement("android.view.LinearLayout", null, null) },
+ new SimpleElement[] { new SimpleElement("android.view.LinearLayout",
+ null, null, null) },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout\n" +
+ "{V=3,N=android.view.LinearLayout\n" +
"}\n"));
assertArrayEquals(
new SimpleElement[] { new SimpleElement("android.view.LinearLayout",
"android.view.FrameLayout",
- null) },
+ null, null) },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout\n" +
+ "{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout\n" +
"}\n"));
assertArrayEquals(
new SimpleElement[] { new SimpleElement("android.view.LinearLayout",
null,
- new Rect(10, 5, 60, 40)) },
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)) },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,R=10 5 60 40\n" +
+ "{V=3,N=android.view.LinearLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"}\n"));
assertArrayEquals(
new SimpleElement[] { e },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ "{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"}\n"));
@@ -150,7 +159,7 @@ public class SimpleElementTest extends TestCase {
assertArrayEquals(
new SimpleElement[] { e },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ "{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"@name:uri=value\n" +
"@second-name:my-uri=my = value \n" +
"}\n"));
@@ -158,10 +167,12 @@ public class SimpleElementTest extends TestCase {
SimpleElement e2 = new SimpleElement("android.view.Button",
"android.view.LinearLayout",
- new Rect(10, 20, 30, 40));
+ new Rect(10, 20, 30, 40),
+ new Rect(0, 0, 320, 480));
e2.addAttribute(new SimpleAttribute("uri1", "name1", "value1"));
SimpleElement e3 = new SimpleElement("android.view.CheckBox",
"android.view.LinearLayout",
+ new Rect(-1, -2, -3, -4),
new Rect(-1, -2, -3, -4));
e3.addAttribute(new SimpleAttribute("uri2", "name2", "value2"));
e3.addAttribute(new SimpleAttribute("uri3", "name3", "value3"));
@@ -171,13 +182,13 @@ public class SimpleElementTest extends TestCase {
assertArrayEquals(
new SimpleElement[] { e },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ "{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"@name:uri=value\n" +
"@second-name:my-uri=my = value \n" +
- "{V=2,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40\n" +
+ "{V=3,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40,Q=0 0 320 480\n" +
"@name1:uri1=value1\n" +
"}\n" +
- "{V=2,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4\n" +
+ "{V=3,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4,Q=-1 -2 -3 -4\n" +
"@name2:uri2=value2\n" +
"@name3:uri3=value3\n" +
"}\n" +
@@ -187,21 +198,21 @@ public class SimpleElementTest extends TestCase {
assertArrayEquals(
new SimpleElement[] { e, e2, e3 },
SimpleElement.parseString(
- "{V=2,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40\n" +
+ "{V=3,N=android.view.LinearLayout,P=android.view.FrameLayout,R=10 5 60 40,Q=0 0 320 480\n" +
"@name:uri=value\n" +
"@second-name:my-uri=my = value \n" +
- "{V=2,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40\n" +
+ "{V=3,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40,Q=0 0 320 480\n" +
"@name1:uri1=value1\n" +
"}\n" +
- "{V=2,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4\n" +
+ "{V=3,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4\n" +
"@name2:uri2=value2\n" +
"@name3:uri3=value3\n" +
"}\n" +
"}\n" +
- "{V=2,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40\n" +
+ "{V=3,N=android.view.Button,P=android.view.LinearLayout,R=10 20 30 40,Q=0 0 320 480\n" +
"@name1:uri1=value1\n" +
"}\n" +
- "{V=2,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4\n" +
+ "{V=3,N=android.view.CheckBox,P=android.view.LinearLayout,R=-1 -2 -3 -4,Q=-1 -2 -3 -4\n" +
"@name2:uri2=value2\n" +
"@name3:uri3=value3\n" +
"}\n"));
@@ -239,16 +250,16 @@ public class SimpleElementTest extends TestCase {
new SimpleElement[] {},
e.getInnerElements());
- e.addInnerElement(new SimpleElement("android.view.Button", null, null));
+ e.addInnerElement(new SimpleElement("android.view.Button", null, null, null));
assertArrayEquals(
- new SimpleElement[] { new SimpleElement("android.view.Button", null, null) },
+ new SimpleElement[] { new SimpleElement("android.view.Button", null, null, null) },
e.getInnerElements());
- e.addInnerElement(new SimpleElement("android.view.CheckBox", null, null));
+ e.addInnerElement(new SimpleElement("android.view.CheckBox", null, null, null));
assertArrayEquals(
- new SimpleElement[] { new SimpleElement("android.view.Button", null, null),
- new SimpleElement("android.view.CheckBox", null, null) },
- e.getInnerElements());
+ new SimpleElement[] { new SimpleElement("android.view.Button", null, null, null),
+ new SimpleElement("android.view.CheckBox", null, null, null) },
+ e.getInnerElements());
}
public final void testEqualsObject() {
@@ -257,30 +268,42 @@ public class SimpleElementTest extends TestCase {
assertNotSame(new SimpleElement("android.view.LinearLayout",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40)),
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)),
e);
assertEquals(new SimpleElement("android.view.LinearLayout",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40)),
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)),
e);
assertTrue(e.equals(new SimpleElement("android.view.LinearLayout",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40))));
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480))));
// not the same FQCN
assertFalse(e.equals(new SimpleElement("android.view.Button",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40))));
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480))));
// not the same parent
assertFalse(e.equals(new SimpleElement("android.view.LinearLayout",
"android.view.LinearLayout",
- new Rect(10, 5, 60, 40))));
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480))));
// not the same bounds
assertFalse(e.equals(new SimpleElement("android.view.LinearLayout",
- "android.view.LinearLayout",
- new Rect(10, 25, 30, 40))));
+ "android.view.FrameLayout",
+ new Rect(10, 25, 30, 40),
+ new Rect(0, 0, 320, 480))));
+
+ // not the same parent bounds
+ assertFalse(e.equals(new SimpleElement("android.view.LinearLayout",
+ "android.view.FrameLayout",
+ new Rect(10, 5, 60, 40),
+ new Rect(10, 100, 160, 240))));
}
public final void testHashCode() {
@@ -288,23 +311,33 @@ public class SimpleElementTest extends TestCase {
assertEquals(he, new SimpleElement("android.view.LinearLayout",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40)).hashCode());
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)).hashCode());
// not the same FQCN
assertFalse(he == new SimpleElement("android.view.Button",
"android.view.FrameLayout",
- new Rect(10, 5, 60, 40)).hashCode());
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)).hashCode());
// not the same parent
assertFalse(he == new SimpleElement("android.view.LinearLayout",
"android.view.Button",
- new Rect(10, 5, 60, 40)).hashCode());
+ new Rect(10, 5, 60, 40),
+ new Rect(0, 0, 320, 480)).hashCode());
// not the same bounds
assertFalse(he == new SimpleElement("android.view.LinearLayout",
- "android.view.LinearLayout",
- new Rect(10, 25, 30, 40)).hashCode());
+ "android.view.FrameLayout",
+ new Rect(10, 25, 30, 40),
+ new Rect(0, 0, 320, 480)).hashCode());
+
+ // not the same parent bounds
+ assertFalse(he == new SimpleElement("android.view.LinearLayout",
+ "android.view.FrameLayout",
+ new Rect(10, 25, 30, 40),
+ new Rect(10, 100, 160, 240)).hashCode());
}
}