• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
dragIntoEmpty(Rect dragBounds)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
dragInto(boolean vertical, Rect dragBounds, Point dragPoint, int insertIndex, int currentIndex, String... graphicsFragments)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
testContextMenu()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 
testContextMenuCustom()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
testOrientation()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
testProperties()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 
testDragInEmptyWithBounds()284     public void testDragInEmptyWithBounds() {
285         dragIntoEmpty(new Rect(0, 0, 100, 80));
286     }
287 
testDragInEmptyWithoutBounds()288     public void testDragInEmptyWithoutBounds() {
289         dragIntoEmpty(new Rect(0, 0, 0, 0));
290     }
291 
testDragInVerticalTop()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 
testDragInVerticalBottom()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 
testDragInVerticalMiddle()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 
testDragInVerticalMiddleSelfPos()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 
testDragToLastPosition()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 
testFormatFloatValue()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 
testFormatFloatValueLocale()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