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