diff options
author | Tor Norbye <tnorbye@google.com> | 2012-08-07 11:48:03 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-08-08 15:18:48 -0700 |
commit | 0cb8e647ef345f5c2a6b7eb08b517421131bca4e (patch) | |
tree | 0952bed29c8c937cb6a2e0a6339efbe234e3e9a7 /eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout | |
parent | 880f28a8a6f1abde140e5a201e5d38f62a8db1ae (diff) | |
download | sdk-0cb8e647ef345f5c2a6b7eb08b517421131bca4e.zip sdk-0cb8e647ef345f5c2a6b7eb08b517421131bca4e.tar.gz sdk-0cb8e647ef345f5c2a6b7eb08b517421131bca4e.tar.bz2 |
Improvements to relative layout move and delete operations
This changeset improves the way the RelativeLayout editing support in
the layout editor handles deletions and moves.
First, during a move, if the move is simply within the same layout,
then the layout constraints are left alone such that if you for
example have
A
v
B < C < D
and you move B up to be next to A, you end up with
A < B < C < D
(It will however remove cycles if the move would result in them.)
Second, it now handles deletion better where deleting a view will
cause all references to any deleted views to be replaced by transitive
constraints.
For example, if you hve
A < B < C < D
and you delete B and C, you end up with
A < D
Change-Id: Icb9d3552e60aee20192f7941fe52be71ba52557f
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout')
2 files changed, 491 insertions, 1 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java index b9176f6..372e329 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java @@ -36,8 +36,10 @@ import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; import com.google.common.base.Splitter; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import java.io.IOException; import java.io.StringWriter; @@ -336,8 +338,24 @@ public class TestNode implements INode { } @Override + public @NonNull IAttribute[] getLiveAttributes() { + List<IAttribute> result = new ArrayList<IAttribute>(); + + NamedNodeMap attributes = mElement.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + result.add(new TestXmlAttribute(attribute)); + } + return result.toArray(new IAttribute[result.size()]); + } + + @Override public boolean setAttribute(String uri, String localName, String value) { - mElement.setAttributeNS(uri, localName, value); + if (value == null) { + mElement.removeAttributeNS(uri, localName); + } else { + mElement.setAttributeNS(uri, localName, value); + } return super.setAttribute(uri, localName, value); } @@ -395,6 +413,33 @@ public class TestNode implements INode { } } + public static class TestXmlAttribute implements IAttribute { + private Attr mAttribute; + + public TestXmlAttribute(Attr attribute) { + this.mAttribute = attribute; + } + + @Override + public String getUri() { + return mAttribute.getNamespaceURI(); + } + + @Override + public String getName() { + String name = mAttribute.getLocalName(); + if (name == null) { + name = mAttribute.getName(); + } + return name; + } + + @Override + public String getValue() { + return mAttribute.getValue(); + } + } + // Recursively initialize this node with the bounds specified in the given hierarchy // dump (from ViewHierarchy's DUMP_INFO flag public void assignBounds(String bounds) { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java new file mode 100644 index 0000000..f5ec1ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2012 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.common.layout.relative; + +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; + +import com.android.ide.common.api.INode; +import com.android.ide.common.layout.BaseViewRule; +import com.android.ide.common.layout.TestNode; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class DeletionHandlerTest extends TestCase { + public void testSimple() { + String xml = "" + + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\" >\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:text=\"A\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button1\"\n" + + " android:layout_alignBottom=\"@+id/button1\"\n" + + " android:layout_toRightOf=\"@+id/button1\"\n" + + " android:text=\"B\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button2\"\n" + + " android:layout_toRightOf=\"@+id/button2\"\n" + + " android:text=\"C\" />\n" + + "\n" + + "</RelativeLayout>"; + TestNode targetNode = TestNode.createFromXml(xml); + assertNotNull(targetNode); + + TestNode button2 = TestNode.findById(targetNode, "@+id/button2"); + + INode layout = button2.getParent(); + List<INode> deletedNodes = Collections.<INode>singletonList(button2); + List<INode> movedNodes = Collections.<INode>emptyList(); + assertSame(layout, targetNode); + layout.removeChild(button2); + + DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout); + handler.updateConstraints(); + + String updated = TestNode.toXml(targetNode); + assertEquals( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\">\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:text=\"A\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button1\"\n" + + " android:layout_alignBottom=\"@+id/button1\"\n" + + " android:layout_toRightOf=\"@+id/button1\"\n" + + " android:text=\"C\">\n" + + " </Button>\n" + + "\n" + + "</RelativeLayout>", + updated); + assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button2.getStringAttr(ANDROID_URI, + ATTR_ID)))); + } + + public void testTransitive() { + String xml = "" + + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\" >\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_alignParentTop=\"true\"\n" + + " android:text=\"Above\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_below=\"@+id/button1\"\n" + + " android:text=\"A\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button2\"\n" + + " android:layout_alignBottom=\"@+id/button2\"\n" + + " android:layout_toRightOf=\"@+id/button2\"\n" + + " android:text=\"B\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button4\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button3\"\n" + + " android:layout_toRightOf=\"@+id/button3\"\n" + + " android:text=\"C\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button5\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button4\"\n" + + " android:layout_alignBottom=\"@+id/button4\"\n" + + " android:layout_toRightOf=\"@+id/button4\"\n" + + " android:text=\"D\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button6\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button5\"\n" + + " android:layout_toRightOf=\"@+id/button5\"\n" + + " android:text=\"E\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button7\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignLeft=\"@+id/button3\"\n" + + " android:layout_below=\"@+id/button3\"\n" + + " android:text=\"Button\" />\n" + + "\n" + + " <CheckBox\n" + + " android:id=\"@+id/checkBox1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button7\"\n" + + " android:layout_alignBottom=\"@+id/button7\"\n" + + " android:layout_toRightOf=\"@+id/button7\"\n" + + " android:text=\"CheckBox\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button8\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_below=\"@+id/checkBox1\"\n" + + " android:layout_toRightOf=\"@+id/checkBox1\"\n" + + " android:text=\"Button\" />\n" + + "\n" + + "</RelativeLayout>"; + TestNode targetNode = TestNode.createFromXml(xml); + assertNotNull(targetNode); + TestNode button7 = TestNode.findById(targetNode, "@+id/button7"); + TestNode checkBox = TestNode.findById(targetNode, "@+id/checkBox1"); + + INode layout = button7.getParent(); + List<INode> deletedNodes = Arrays.<INode>asList(button7, checkBox); + List<INode> movedNodes = Collections.<INode>emptyList(); + assertSame(layout, targetNode); + layout.removeChild(button7); + layout.removeChild(checkBox); + + DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout); + handler.updateConstraints(); + + String updated = TestNode.toXml(targetNode); + assertEquals( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\">\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_alignParentTop=\"true\"\n" + + " android:text=\"Above\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_below=\"@+id/button1\"\n" + + " android:text=\"A\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button2\"\n" + + " android:layout_alignBottom=\"@+id/button2\"\n" + + " android:layout_toRightOf=\"@+id/button2\"\n" + + " android:text=\"B\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button4\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button3\"\n" + + " android:layout_toRightOf=\"@+id/button3\"\n" + + " android:text=\"C\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button5\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button4\"\n" + + " android:layout_alignBottom=\"@+id/button4\"\n" + + " android:layout_toRightOf=\"@+id/button4\"\n" + + " android:text=\"D\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button6\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button5\"\n" + + " android:layout_toRightOf=\"@+id/button5\"\n" + + " android:text=\"E\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button8\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignLeft=\"@+id/button3\"\n" + + " android:layout_below=\"@+id/button3\"\n" + + " android:text=\"Button\">\n" + + " </Button>\n" + + "\n" + + "</RelativeLayout>", + updated); + assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button7.getStringAttr(ANDROID_URI, + ATTR_ID)))); + } + + public void testCenter() { + String xml = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\" >\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_centerInParent=\"true\"\n" + + " android:text=\"Button\" />\n" + + "\n" + + " <CheckBox\n" + + " android:id=\"@+id/checkBox1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_below=\"@+id/button1\"\n" + + " android:layout_toRightOf=\"@+id/button1\"\n" + + " android:text=\"CheckBox\" />\n" + + "\n" + + "</RelativeLayout>"; + + TestNode targetNode = TestNode.createFromXml(xml); + assertNotNull(targetNode); + TestNode button1 = TestNode.findById(targetNode, "@+id/button1"); + + INode layout = button1.getParent(); + List<INode> deletedNodes = Collections.<INode>singletonList(button1); + List<INode> movedNodes = Collections.<INode>emptyList(); + assertSame(layout, targetNode); + layout.removeChild(button1); + + DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout); + handler.updateConstraints(); + + String updated = TestNode.toXml(targetNode); + assertEquals( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\">\n" + + "\n" + + " <CheckBox\n" + + " android:id=\"@+id/checkBox1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_centerInParent=\"true\"\n" + + " android:text=\"CheckBox\">\n" + + " </CheckBox>\n" + + "\n" + + "</RelativeLayout>", + updated); + assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button1.getStringAttr(ANDROID_URI, + ATTR_ID)))); + + } + + public void testMove() { + String xml = "" + + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\" >\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:text=\"A\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button1\"\n" + + " android:layout_alignBottom=\"@+id/button1\"\n" + + " android:layout_toRightOf=\"@+id/button1\"\n" + + " android:text=\"B\" />\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button2\"\n" + + " android:layout_toRightOf=\"@+id/button2\"\n" + + " android:text=\"C\" />\n" + + "\n" + + "</RelativeLayout>"; + TestNode targetNode = TestNode.createFromXml(xml); + assertNotNull(targetNode); + + TestNode button2 = TestNode.findById(targetNode, "@+id/button2"); + + INode layout = button2.getParent(); + List<INode> deletedNodes = Collections.<INode>singletonList(button2); + List<INode> movedNodes = Collections.<INode>singletonList(button2); + assertSame(layout, targetNode); + + DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout); + handler.updateConstraints(); + + String updated = TestNode.toXml(targetNode); + assertEquals( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " tools:ignore=\"HardcodedText\">\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button1\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:text=\"A\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button2\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBaseline=\"@+id/button1\"\n" + + " android:layout_alignBottom=\"@+id/button1\"\n" + + " android:layout_toRightOf=\"@+id/button1\"\n" + + " android:text=\"B\">\n" + + " </Button>\n" + + "\n" + + " <Button\n" + + " android:id=\"@+id/button3\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_alignBottom=\"@+id/button2\"\n" + + " android:layout_toRightOf=\"@+id/button2\"\n" + + " android:text=\"C\">\n" + + " </Button>\n" + + "\n" + + "</RelativeLayout>", + updated); + assertTrue(updated.contains(BaseViewRule.stripIdPrefix(button2.getStringAttr(ANDROID_URI, + ATTR_ID)))); + } +} |