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