Home | History | Annotate | Download | only in layout
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.common.layout;
     18 
     19 import static com.android.SdkConstants.ANDROID_URI;
     20 import static com.android.SdkConstants.ATTR_ID;
     21 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
     22 import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
     23 import static com.android.SdkConstants.ATTR_ORIENTATION;
     24 import static com.android.SdkConstants.VALUE_HORIZONTAL;
     25 import static com.android.SdkConstants.VALUE_VERTICAL;
     26 
     27 import com.android.ide.common.api.DropFeedback;
     28 import com.android.ide.common.api.IAttributeInfo.Format;
     29 import com.android.ide.common.api.IDragElement;
     30 import com.android.ide.common.api.IMenuCallback;
     31 import com.android.ide.common.api.INode;
     32 import com.android.ide.common.api.IViewRule;
     33 import com.android.ide.common.api.Point;
     34 import com.android.ide.common.api.Rect;
     35 import com.android.ide.common.api.RuleAction;
     36 import com.android.ide.common.api.RuleAction.NestedAction;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Arrays;
     40 import java.util.Collections;
     41 import java.util.List;
     42 
     43 /** Test the {@link LinearLayoutRule} */
     44 public class LinearLayoutRuleTest extends LayoutTestBase {
     45     // Utility for other tests
     46     protected void dragIntoEmpty(Rect dragBounds) {
     47         boolean haveBounds = dragBounds.isValid();
     48 
     49         IViewRule rule = new LinearLayoutRule();
     50 
     51         INode targetNode = TestNode.create("android.widget.LinearLayout").id(
     52         "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480));
     53         Point dropPoint = new Point(10, 5);
     54 
     55         IDragElement[] elements = TestDragElement.create(TestDragElement.create(
     56                 "android.widget.Button", dragBounds).id("@+id/Button01"));
     57 
     58         // Enter target
     59         DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
     60         assertNotNull(feedback);
     61         assertFalse(feedback.invalidTarget);
     62         assertNotNull(feedback.painter);
     63 
     64         feedback = rule.onDropMove(targetNode, elements, feedback, dropPoint);
     65         assertNotNull(feedback);
     66         assertFalse(feedback.invalidTarget);
     67 
     68         // Paint feedback and make sure it's what we expect
     69         TestGraphics graphics = new TestGraphics();
     70         assertNotNull(feedback.painter);
     71         feedback.painter.paint(graphics, targetNode, feedback);
     72         assertEquals(
     73                 // Expect to see a recipient rectangle around the bounds of the
     74                 // LinearLayout,
     75                 // as well as a single vertical line as a drop preview located
     76                 // along the left
     77                 // edge (for this horizontal linear layout) showing insert
     78                 // position at index 0,
     79                 // and finally a rectangle for the bounds of the inserted button
     80                 // centered over
     81                 // the middle
     82                 "[useStyle(DROP_RECIPIENT), "
     83                         +
     84                         // Bounds rectangle
     85                         "drawRect(Rect[0,0,240,480]), "
     86                         + "useStyle(DROP_ZONE), drawLine(1,0,1,480), "
     87                         + "useStyle(DROP_ZONE_ACTIVE), " + "useStyle(DROP_PREVIEW), " +
     88                         // Insert position line
     89                         "drawLine(1,0,1,480)" + (haveBounds ?
     90                         // Outline of dragged node centered over position line
     91                         ", useStyle(DROP_PREVIEW), " + "drawRect(1,0,101,80)"
     92                                 // Nothing when we don't have bounds
     93                                 : "") + "]", graphics.getDrawn().toString());
     94 
     95         // Attempt a drop
     96         assertEquals(0, targetNode.getChildren().length);
     97         rule.onDropped(targetNode, elements, feedback, dropPoint);
     98         assertEquals(1, targetNode.getChildren().length);
     99         assertEquals("@+id/Button01", targetNode.getChildren()[0].getStringAttr(
    100                 ANDROID_URI, ATTR_ID));
    101     }
    102 
    103     // Utility for other tests
    104     protected INode dragInto(boolean vertical, Rect dragBounds, Point dragPoint,
    105             int insertIndex, int currentIndex,
    106             String... graphicsFragments) {
    107         INode linearLayout = TestNode.create("android.widget.LinearLayout").id(
    108                 "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI,
    109                 ATTR_ORIENTATION,
    110                 vertical ? VALUE_VERTICAL : VALUE_HORIZONTAL)
    111                 .add(
    112                         TestNode.create("android.widget.Button").id("@+id/Button01").bounds(
    113                                 new Rect(0, 0, 100, 80)),
    114                         TestNode.create("android.widget.Button").id("@+id/Button02").bounds(
    115                                 new Rect(0, 100, 100, 80)),
    116                         TestNode.create("android.widget.Button").id("@+id/Button03").bounds(
    117                                 new Rect(0, 200, 100, 80)),
    118                         TestNode.create("android.widget.Button").id("@+id/Button04").bounds(
    119                                 new Rect(0, 300, 100, 80)));
    120 
    121         return super.dragInto(new LinearLayoutRule(), linearLayout, dragBounds, dragPoint, null,
    122                 insertIndex, currentIndex, graphicsFragments);
    123     }
    124 
    125     // Check that the context menu registers the expected menu items
    126     public void testContextMenu() {
    127         LinearLayoutRule rule = new LinearLayoutRule();
    128         initialize(rule, "android.widget.LinearLayout");
    129         INode node = TestNode.create("android.widget.Button").id("@+id/Button012");
    130 
    131         List<RuleAction> contextMenu = new ArrayList<RuleAction>();
    132         rule.addContextMenuActions(contextMenu, node);
    133         assertEquals(6, contextMenu.size());
    134         assertEquals("Edit ID...", contextMenu.get(0).getTitle());
    135         assertTrue(contextMenu.get(1) instanceof RuleAction.Separator);
    136         assertEquals("Layout Width", contextMenu.get(2).getTitle());
    137         assertEquals("Layout Height", contextMenu.get(3).getTitle());
    138         assertTrue(contextMenu.get(4) instanceof RuleAction.Separator);
    139         assertEquals("Other Properties", contextMenu.get(5).getTitle());
    140 
    141         RuleAction propertiesMenu = contextMenu.get(5);
    142         assertTrue(propertiesMenu.getClass().getName(),
    143                 propertiesMenu instanceof NestedAction);
    144     }
    145 
    146     public void testContextMenuCustom() {
    147         LinearLayoutRule rule = new LinearLayoutRule();
    148         initialize(rule, "android.widget.LinearLayout");
    149         INode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout")
    150             .set(ANDROID_URI, ATTR_LAYOUT_WIDTH, "42dip")
    151             .set(ANDROID_URI, ATTR_LAYOUT_HEIGHT, "50sp");
    152 
    153         List<RuleAction> contextMenu = new ArrayList<RuleAction>();
    154         rule.addContextMenuActions(contextMenu, node);
    155         assertEquals(6, contextMenu.size());
    156         assertEquals("Layout Width", contextMenu.get(2).getTitle());
    157         RuleAction menuAction = contextMenu.get(2);
    158         assertTrue(menuAction instanceof RuleAction.Choices);
    159         RuleAction.Choices choices = (RuleAction.Choices) menuAction;
    160         List<String> titles = choices.getTitles();
    161         List<String> ids = choices.getIds();
    162         assertEquals("Wrap Content", titles.get(0));
    163         assertEquals("wrap_content", ids.get(0));
    164         assertEquals("Match Parent", titles.get(1));
    165         assertEquals("match_parent", ids.get(1));
    166         assertEquals("42dip", titles.get(2));
    167         assertEquals("42dip", ids.get(2));
    168         assertEquals("42dip", choices.getCurrent());
    169     }
    170 
    171     // Check that the context menu manipulates the orientation attribute
    172     public void testOrientation() {
    173         LinearLayoutRule rule = new LinearLayoutRule();
    174         initialize(rule, "android.widget.LinearLayout");
    175         TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012");
    176         node.putAttributeInfo(ANDROID_URI, "orientation",
    177                 new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET,
    178                         "android.widget.LinearLayout",
    179                         new String[] {"horizontal", "vertical"}, null, null));
    180 
    181         assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION));
    182 
    183         List<RuleAction> contextMenu = new ArrayList<RuleAction>();
    184         rule.addContextMenuActions(contextMenu, node);
    185         assertEquals(7, contextMenu.size());
    186         RuleAction orientationAction = contextMenu.get(1);
    187         assertEquals("Orientation", orientationAction.getTitle());
    188 
    189         assertTrue(orientationAction.getClass().getName(),
    190                 orientationAction instanceof RuleAction.Choices);
    191 
    192         RuleAction.Choices choices = (RuleAction.Choices) orientationAction;
    193         IMenuCallback callback = choices.getCallback();
    194         callback.action(orientationAction, Collections.singletonList(node), VALUE_VERTICAL, true);
    195 
    196         String orientation = node.getStringAttr(ANDROID_URI,
    197                 ATTR_ORIENTATION);
    198         assertEquals(VALUE_VERTICAL, orientation);
    199         callback.action(orientationAction, Collections.singletonList(node), VALUE_HORIZONTAL,
    200                 true);
    201         orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION);
    202         assertEquals(VALUE_HORIZONTAL, orientation);
    203     }
    204 
    205     // Check that the context menu manipulates the orientation attribute
    206     public void testProperties() {
    207         LinearLayoutRule rule = new LinearLayoutRule();
    208         initialize(rule, "android.widget.LinearLayout");
    209         TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012");
    210         node.putAttributeInfo(ANDROID_URI, "orientation",
    211                 new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET,
    212                         "android.widget.LinearLayout",
    213                         new String[] {"horizontal", "vertical"}, null, null));
    214         node.setAttributeSources(Arrays.asList("android.widget.LinearLayout",
    215                 "android.view.ViewGroup", "android.view.View"));
    216         node.putAttributeInfo(ANDROID_URI, "gravity",
    217                 new TestAttributeInfo("gravity", Format.INTEGER_SET,
    218                         "android.widget.LinearLayout", null, null, null));
    219 
    220 
    221         assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION));
    222 
    223         List<RuleAction> contextMenu = new ArrayList<RuleAction>();
    224         rule.addContextMenuActions(contextMenu, node);
    225         assertEquals(8, contextMenu.size());
    226 
    227         assertEquals("Orientation", contextMenu.get(1).getTitle());
    228         assertEquals("Edit Gravity...", contextMenu.get(2).getTitle());
    229 
    230         assertEquals("Other Properties", contextMenu.get(7).getTitle());
    231 
    232         RuleAction propertiesMenu = contextMenu.get(7);
    233         assertTrue(propertiesMenu.getClass().getName(),
    234                 propertiesMenu instanceof NestedAction);
    235         NestedAction nested = (NestedAction) propertiesMenu;
    236         List<RuleAction> nestedActions = nested.getNestedActions(node);
    237         assertEquals(9, nestedActions.size());
    238         assertEquals("Recent", nestedActions.get(0).getTitle());
    239         assertTrue(nestedActions.get(1) instanceof RuleAction.Separator);
    240         assertEquals("Defined by LinearLayout", nestedActions.get(2).getTitle());
    241         assertEquals("Inherited from ViewGroup", nestedActions.get(3).getTitle());
    242         assertEquals("Inherited from View", nestedActions.get(4).getTitle());
    243         assertTrue(nestedActions.get(5) instanceof RuleAction.Separator);
    244         assertEquals("Layout Parameters", nestedActions.get(6).getTitle());
    245         assertTrue(nestedActions.get(7) instanceof RuleAction.Separator);
    246         assertEquals("All By Name", nestedActions.get(8).getTitle());
    247 
    248         BaseViewRule.editedProperty(ATTR_ORIENTATION);
    249 
    250         RuleAction recentAction = nestedActions.get(0);
    251         assertTrue(recentAction instanceof NestedAction);
    252         NestedAction recentChoices = (NestedAction) recentAction;
    253         List<RuleAction> recentItems = recentChoices.getNestedActions(node);
    254 
    255         assertEquals(1, recentItems.size());
    256         assertEquals("Orientation", recentItems.get(0).getTitle());
    257 
    258         BaseViewRule.editedProperty("gravity");
    259         recentItems = recentChoices.getNestedActions(node);
    260         assertEquals(2, recentItems.size());
    261         assertEquals("Gravity...", recentItems.get(0).getTitle());
    262         assertEquals("Orientation", recentItems.get(1).getTitle());
    263 
    264         BaseViewRule.editedProperty(ATTR_ORIENTATION);
    265         recentItems = recentChoices.getNestedActions(node);
    266         assertEquals(2, recentItems.size());
    267         assertEquals("Orientation", recentItems.get(0).getTitle());
    268         assertEquals("Gravity...", recentItems.get(1).getTitle());
    269 
    270         // Lots of other properties -- flushes out properties that apply to this view
    271         for (int i = 0; i < 30; i++) {
    272             BaseViewRule.editedProperty("dummy_" + i);
    273         }
    274         recentItems = recentChoices.getNestedActions(node);
    275         assertEquals(0, recentItems.size());
    276 
    277         BaseViewRule.editedProperty("gravity");
    278         recentItems = recentChoices.getNestedActions(node);
    279         assertEquals(1, recentItems.size());
    280         assertEquals("Gravity...", recentItems.get(0).getTitle());
    281     }
    282 
    283     public void testDragInEmptyWithBounds() {
    284         dragIntoEmpty(new Rect(0, 0, 100, 80));
    285     }
    286 
    287     public void testDragInEmptyWithoutBounds() {
    288         dragIntoEmpty(new Rect(0, 0, 0, 0));
    289     }
    290 
    291     public void testDragInVerticalTop() {
    292         dragInto(true,
    293                 // Bounds of the dragged item
    294                 new Rect(0, 0, 105, 80),
    295                 // Drag point
    296                 new Point(30, -10),
    297                 // Expected insert location
    298                 0,
    299                 // Not dragging one of the existing children
    300                 -1,
    301                 // Bounds rectangle
    302                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    303 
    304                 // Drop zones
    305                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
    306                         + "drawLine(0,190,240,190), drawLine(0,290,240,290), "
    307                         + "drawLine(0,381,240,381)",
    308 
    309                 // Active nearest line
    310                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)",
    311 
    312                 // Preview of the dropped rectangle
    313                 "useStyle(DROP_PREVIEW), drawRect(0,-40,105,40)");
    314 
    315         // Without drag bounds it should be identical except no preview
    316         // rectangle
    317         dragInto(true,
    318                 new Rect(0, 0, 0, 0), // Invalid
    319                 new Point(30, -10), 0, -1,
    320                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)");
    321     }
    322 
    323     public void testDragInVerticalBottom() {
    324         dragInto(true,
    325                 // Bounds of the dragged item
    326                 new Rect(0, 0, 105, 80),
    327                 // Drag point
    328                 new Point(30, 500),
    329                 // Expected insert location
    330                 4,
    331                 // Not dragging one of the existing children
    332                 -1,
    333                 // Bounds rectangle
    334                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    335 
    336                 // Drop zones
    337                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
    338                         + "drawLine(0,190,240,190), drawLine(0,290,240,290), drawLine(0,381,240,381), ",
    339 
    340                 // Active nearest line
    341                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)",
    342 
    343                 // Preview of the dropped rectangle
    344                 "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)");
    345 
    346         // Check without bounds too
    347         dragInto(true, new Rect(0, 0, 105, 80), new Point(30, 500), 4, -1,
    348                 "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)");
    349     }
    350 
    351     public void testDragInVerticalMiddle() {
    352         dragInto(true,
    353                 // Bounds of the dragged item
    354                 new Rect(0, 0, 105, 80),
    355                 // Drag point
    356                 new Point(0, 170),
    357                 // Expected insert location
    358                 2,
    359                 // Not dragging one of the existing children
    360                 -1,
    361                 // Bounds rectangle
    362                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    363 
    364                 // Drop zones
    365                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
    366                         + "drawLine(0,190,240,190), drawLine(0,290,240,290)",
    367 
    368                 // Active nearest line
    369                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,190,240,190)",
    370 
    371                 // Preview of the dropped rectangle
    372                 "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)");
    373 
    374         // Check without bounds too
    375         dragInto(true, new Rect(0, 0, 105, 80), new Point(0, 170), 2, -1,
    376                 "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)");
    377     }
    378 
    379     public void testDragInVerticalMiddleSelfPos() {
    380         // Drag the 2nd button, down to the position between 3rd and 4th
    381         dragInto(true,
    382                 // Bounds of the dragged item
    383                 new Rect(0, 100, 100, 80),
    384                 // Drag point
    385                 new Point(0, 250),
    386                 // Expected insert location
    387                 2,
    388                 // Dragging 1st item
    389                 1,
    390                 // Bounds rectangle
    391 
    392                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    393 
    394                 // Drop zones - these are different because we exclude drop
    395                 // zones around the
    396                 // dragged item itself (it doesn't make sense to insert directly
    397                 // before or after
    398                 // myself
    399                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), "
    400                         + "drawLine(0,381,240,381)",
    401 
    402                 // Preview line along insert axis
    403                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,290,240,290)",
    404 
    405                 // Preview of dropped rectangle
    406                 "useStyle(DROP_PREVIEW), drawRect(0,250,100,330)");
    407 
    408         // Test dropping on self (no position change):
    409         dragInto(true,
    410                 // Bounds of the dragged item
    411                 new Rect(0, 100, 100, 80),
    412                 // Drag point
    413                 new Point(0, 210),
    414                 // Expected insert location
    415                 1,
    416                 // Dragging from same pos
    417                 1,
    418                 // Bounds rectangle
    419                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    420 
    421                 // Drop zones - these are different because we exclude drop
    422                 // zones around the
    423                 // dragged item itself (it doesn't make sense to insert directly
    424                 // before or after
    425                 // myself
    426                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), "
    427                         + "drawLine(0,381,240,381)",
    428 
    429                 // No active nearest line when you're over the self pos!
    430 
    431                 // Preview of the dropped rectangle
    432                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawRect(0,100,100,180)");
    433     }
    434 
    435     public void testDragToLastPosition() {
    436         // Drag a button to the last position -- and confirm that the preview rectangle
    437         // is now shown midway between the second to last and last positions, but fully
    438         // below the drop zone line:
    439         dragInto(true,
    440                 // Bounds of the dragged item
    441                 new Rect(0, 100, 100, 80),
    442                 // Drag point
    443                 new Point(0, 400),
    444                 // Expected insert location
    445                 3,
    446                 // Dragging 1st item
    447                 1,
    448 
    449                 // Bounds rectangle
    450                 "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
    451 
    452                 // Drop Zones
    453                 "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), " +
    454                 "drawLine(0,381,240,381), ",
    455 
    456                 // Active Drop Zone
    457                 "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)",
    458 
    459                 // Drop Preview
    460                 "useStyle(DROP_PREVIEW), drawRect(0,381,100,461)");
    461     }
    462 
    463     // Left to test:
    464     // Check inserting at last pos with multiple children
    465     // Check inserting with no bounds rectangle for dragged element
    466 }
    467