| /* |
| * 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 |
| } |