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