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