/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.common.layout; import static com.android.SdkConstants.ANDROID_URI; import static com.android.SdkConstants.ATTR_ID; import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; import static com.android.SdkConstants.ATTR_ORIENTATION; import static com.android.SdkConstants.VALUE_HORIZONTAL; import static com.android.SdkConstants.VALUE_VERTICAL; import com.android.ide.common.api.DropFeedback; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.api.IDragElement; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.RuleAction.NestedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** Test the {@link LinearLayoutRule} */ public class LinearLayoutRuleTest extends LayoutTestBase { // Utility for other tests protected void dragIntoEmpty(Rect dragBounds) { boolean haveBounds = dragBounds.isValid(); IViewRule rule = new LinearLayoutRule(); INode targetNode = TestNode.create("android.widget.LinearLayout").id( "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480)); Point dropPoint = new Point(10, 5); IDragElement[] elements = TestDragElement.create(TestDragElement.create( "android.widget.Button", dragBounds).id("@+id/Button01")); // Enter target DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements); assertNotNull(feedback); assertFalse(feedback.invalidTarget); assertNotNull(feedback.painter); feedback = rule.onDropMove(targetNode, elements, feedback, dropPoint); assertNotNull(feedback); assertFalse(feedback.invalidTarget); // Paint feedback and make sure it's what we expect TestGraphics graphics = new TestGraphics(); assertNotNull(feedback.painter); feedback.painter.paint(graphics, targetNode, feedback); assertEquals( // Expect to see a recipient rectangle around the bounds of the // LinearLayout, // as well as a single vertical line as a drop preview located // along the left // edge (for this horizontal linear layout) showing insert // position at index 0, // and finally a rectangle for the bounds of the inserted button // centered over // the middle "[useStyle(DROP_RECIPIENT), " + // Bounds rectangle "drawRect(Rect[0,0,240,480]), " + "useStyle(DROP_ZONE), drawLine(1,0,1,480), " + "useStyle(DROP_ZONE_ACTIVE), " + "useStyle(DROP_PREVIEW), " + // Insert position line "drawLine(1,0,1,480)" + (haveBounds ? // Outline of dragged node centered over position line ", useStyle(DROP_PREVIEW), " + "drawRect(1,0,101,80)" // Nothing when we don't have bounds : "") + "]", graphics.getDrawn().toString()); // Attempt a drop assertEquals(0, targetNode.getChildren().length); rule.onDropped(targetNode, elements, feedback, dropPoint); assertEquals(1, targetNode.getChildren().length); assertEquals("@+id/Button01", targetNode.getChildren()[0].getStringAttr( ANDROID_URI, ATTR_ID)); } // Utility for other tests protected INode dragInto(boolean vertical, Rect dragBounds, Point dragPoint, int insertIndex, int currentIndex, String... graphicsFragments) { INode linearLayout = TestNode.create("android.widget.LinearLayout").id( "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI, ATTR_ORIENTATION, vertical ? VALUE_VERTICAL : VALUE_HORIZONTAL) .add( TestNode.create("android.widget.Button").id("@+id/Button01").bounds( new Rect(0, 0, 100, 80)), TestNode.create("android.widget.Button").id("@+id/Button02").bounds( new Rect(0, 100, 100, 80)), TestNode.create("android.widget.Button").id("@+id/Button03").bounds( new Rect(0, 200, 100, 80)), TestNode.create("android.widget.Button").id("@+id/Button04").bounds( new Rect(0, 300, 100, 80))); return super.dragInto(new LinearLayoutRule(), linearLayout, dragBounds, dragPoint, null, insertIndex, currentIndex, graphicsFragments); } // Check that the context menu registers the expected menu items public void testContextMenu() { LinearLayoutRule rule = new LinearLayoutRule(); initialize(rule, "android.widget.LinearLayout"); INode node = TestNode.create("android.widget.Button").id("@+id/Button012"); List<RuleAction> contextMenu = new ArrayList<RuleAction>(); rule.addContextMenuActions(contextMenu, node); assertEquals(6, contextMenu.size()); assertEquals("Edit ID...", contextMenu.get(0).getTitle()); assertTrue(contextMenu.get(1) instanceof RuleAction.Separator); assertEquals("Layout Width", contextMenu.get(2).getTitle()); assertEquals("Layout Height", contextMenu.get(3).getTitle()); assertTrue(contextMenu.get(4) instanceof RuleAction.Separator); assertEquals("Other Properties", contextMenu.get(5).getTitle()); RuleAction propertiesMenu = contextMenu.get(5); assertTrue(propertiesMenu.getClass().getName(), propertiesMenu instanceof NestedAction); } public void testContextMenuCustom() { LinearLayoutRule rule = new LinearLayoutRule(); initialize(rule, "android.widget.LinearLayout"); INode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout") .set(ANDROID_URI, ATTR_LAYOUT_WIDTH, "42dip") .set(ANDROID_URI, ATTR_LAYOUT_HEIGHT, "50sp"); List<RuleAction> contextMenu = new ArrayList<RuleAction>(); rule.addContextMenuActions(contextMenu, node); assertEquals(6, contextMenu.size()); assertEquals("Layout Width", contextMenu.get(2).getTitle()); RuleAction menuAction = contextMenu.get(2); assertTrue(menuAction instanceof RuleAction.Choices); RuleAction.Choices choices = (RuleAction.Choices) menuAction; List<String> titles = choices.getTitles(); List<String> ids = choices.getIds(); assertEquals("Wrap Content", titles.get(0)); assertEquals("wrap_content", ids.get(0)); assertEquals("Match Parent", titles.get(1)); assertEquals("match_parent", ids.get(1)); assertEquals("42dip", titles.get(2)); assertEquals("42dip", ids.get(2)); assertEquals("42dip", choices.getCurrent()); } // Check that the context menu manipulates the orientation attribute public void testOrientation() { LinearLayoutRule rule = new LinearLayoutRule(); initialize(rule, "android.widget.LinearLayout"); TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012"); node.putAttributeInfo(ANDROID_URI, "orientation", new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET, "android.widget.LinearLayout", new String[] {"horizontal", "vertical"}, null, null)); assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION)); List<RuleAction> contextMenu = new ArrayList<RuleAction>(); rule.addContextMenuActions(contextMenu, node); assertEquals(7, contextMenu.size()); RuleAction orientationAction = contextMenu.get(1); assertEquals("Orientation", orientationAction.getTitle()); assertTrue(orientationAction.getClass().getName(), orientationAction instanceof RuleAction.Choices); RuleAction.Choices choices = (RuleAction.Choices) orientationAction; IMenuCallback callback = choices.getCallback(); callback.action(orientationAction, Collections.singletonList(node), VALUE_VERTICAL, true); String orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); assertEquals(VALUE_VERTICAL, orientation); callback.action(orientationAction, Collections.singletonList(node), VALUE_HORIZONTAL, true); orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); assertEquals(VALUE_HORIZONTAL, orientation); } // Check that the context menu manipulates the orientation attribute public void testProperties() { LinearLayoutRule rule = new LinearLayoutRule(); initialize(rule, "android.widget.LinearLayout"); TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012"); node.putAttributeInfo(ANDROID_URI, "orientation", new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET, "android.widget.LinearLayout", new String[] {"horizontal", "vertical"}, null, null)); node.setAttributeSources(Arrays.asList("android.widget.LinearLayout", "android.view.ViewGroup", "android.view.View")); node.putAttributeInfo(ANDROID_URI, "gravity", new TestAttributeInfo("gravity", Format.INTEGER_SET, "android.widget.LinearLayout", null, null, null)); assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION)); List<RuleAction> contextMenu = new ArrayList<RuleAction>(); rule.addContextMenuActions(contextMenu, node); assertEquals(8, contextMenu.size()); assertEquals("Orientation", contextMenu.get(1).getTitle()); assertEquals("Edit Gravity...", contextMenu.get(2).getTitle()); assertEquals("Other Properties", contextMenu.get(7).getTitle()); RuleAction propertiesMenu = contextMenu.get(7); assertTrue(propertiesMenu.getClass().getName(), propertiesMenu instanceof NestedAction); NestedAction nested = (NestedAction) propertiesMenu; List<RuleAction> nestedActions = nested.getNestedActions(node); assertEquals(9, nestedActions.size()); assertEquals("Recent", nestedActions.get(0).getTitle()); assertTrue(nestedActions.get(1) instanceof RuleAction.Separator); assertEquals("Defined by LinearLayout", nestedActions.get(2).getTitle()); assertEquals("Inherited from ViewGroup", nestedActions.get(3).getTitle()); assertEquals("Inherited from View", nestedActions.get(4).getTitle()); assertTrue(nestedActions.get(5) instanceof RuleAction.Separator); assertEquals("Layout Parameters", nestedActions.get(6).getTitle()); assertTrue(nestedActions.get(7) instanceof RuleAction.Separator); assertEquals("All By Name", nestedActions.get(8).getTitle()); BaseViewRule.editedProperty(ATTR_ORIENTATION); RuleAction recentAction = nestedActions.get(0); assertTrue(recentAction instanceof NestedAction); NestedAction recentChoices = (NestedAction) recentAction; List<RuleAction> recentItems = recentChoices.getNestedActions(node); assertEquals(1, recentItems.size()); assertEquals("Orientation", recentItems.get(0).getTitle()); BaseViewRule.editedProperty("gravity"); recentItems = recentChoices.getNestedActions(node); assertEquals(2, recentItems.size()); assertEquals("Gravity...", recentItems.get(0).getTitle()); assertEquals("Orientation", recentItems.get(1).getTitle()); BaseViewRule.editedProperty(ATTR_ORIENTATION); recentItems = recentChoices.getNestedActions(node); assertEquals(2, recentItems.size()); assertEquals("Orientation", recentItems.get(0).getTitle()); assertEquals("Gravity...", recentItems.get(1).getTitle()); // Lots of other properties -- flushes out properties that apply to this view for (int i = 0; i < 30; i++) { BaseViewRule.editedProperty("dummy_" + i); } recentItems = recentChoices.getNestedActions(node); assertEquals(0, recentItems.size()); BaseViewRule.editedProperty("gravity"); recentItems = recentChoices.getNestedActions(node); assertEquals(1, recentItems.size()); assertEquals("Gravity...", recentItems.get(0).getTitle()); } public void testDragInEmptyWithBounds() { dragIntoEmpty(new Rect(0, 0, 100, 80)); } public void testDragInEmptyWithoutBounds() { dragIntoEmpty(new Rect(0, 0, 0, 0)); } public void testDragInVerticalTop() { dragInto(true, // Bounds of the dragged item new Rect(0, 0, 105, 80), // Drag point new Point(30, -10), // Expected insert location 0, // Not dragging one of the existing children -1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop zones "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), " + "drawLine(0,190,240,190), drawLine(0,290,240,290), " + "drawLine(0,381,240,381)", // Active nearest line "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)", // Preview of the dropped rectangle "useStyle(DROP_PREVIEW), drawRect(0,-40,105,40)"); // Without drag bounds it should be identical except no preview // rectangle dragInto(true, new Rect(0, 0, 0, 0), // Invalid new Point(30, -10), 0, -1, "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)"); } public void testDragInVerticalBottom() { dragInto(true, // Bounds of the dragged item new Rect(0, 0, 105, 80), // Drag point new Point(30, 500), // Expected insert location 4, // Not dragging one of the existing children -1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop zones "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), " + "drawLine(0,190,240,190), drawLine(0,290,240,290), drawLine(0,381,240,381), ", // Active nearest line "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)", // Preview of the dropped rectangle "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)"); // Check without bounds too dragInto(true, new Rect(0, 0, 105, 80), new Point(30, 500), 4, -1, "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)"); } public void testDragInVerticalMiddle() { dragInto(true, // Bounds of the dragged item new Rect(0, 0, 105, 80), // Drag point new Point(0, 170), // Expected insert location 2, // Not dragging one of the existing children -1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop zones "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), " + "drawLine(0,190,240,190), drawLine(0,290,240,290)", // Active nearest line "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,190,240,190)", // Preview of the dropped rectangle "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)"); // Check without bounds too dragInto(true, new Rect(0, 0, 105, 80), new Point(0, 170), 2, -1, "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)"); } public void testDragInVerticalMiddleSelfPos() { // Drag the 2nd button, down to the position between 3rd and 4th dragInto(true, // Bounds of the dragged item new Rect(0, 100, 100, 80), // Drag point new Point(0, 250), // Expected insert location 2, // Dragging 1st item 1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop zones - these are different because we exclude drop // zones around the // dragged item itself (it doesn't make sense to insert directly // before or after // myself "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), " + "drawLine(0,381,240,381)", // Preview line along insert axis "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,290,240,290)", // Preview of dropped rectangle "useStyle(DROP_PREVIEW), drawRect(0,250,100,330)"); // Test dropping on self (no position change): dragInto(true, // Bounds of the dragged item new Rect(0, 100, 100, 80), // Drag point new Point(0, 210), // Expected insert location 1, // Dragging from same pos 1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop zones - these are different because we exclude drop // zones around the // dragged item itself (it doesn't make sense to insert directly // before or after // myself "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), " + "drawLine(0,381,240,381)", // No active nearest line when you're over the self pos! // Preview of the dropped rectangle "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawRect(0,100,100,180)"); } public void testDragToLastPosition() { // Drag a button to the last position -- and confirm that the preview rectangle // is now shown midway between the second to last and last positions, but fully // below the drop zone line: dragInto(true, // Bounds of the dragged item new Rect(0, 100, 100, 80), // Drag point new Point(0, 400), // Expected insert location 3, // Dragging 1st item 1, // Bounds rectangle "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])", // Drop Zones "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), " + "drawLine(0,381,240,381), ", // Active Drop Zone "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)", // Drop Preview "useStyle(DROP_PREVIEW), drawRect(0,381,100,461)"); } // Left to test: // Check inserting at last pos with multiple children // Check inserting with no bounds rectangle for dragged element }