1 /* 2 * Copyright (C) 2012 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 package com.android.ide.eclipse.adt.internal.editors.layout.properties; 17 18 import static com.android.SdkConstants.ATTR_ID; 19 import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; 20 import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; 21 22 import com.android.annotations.Nullable; 23 import com.android.ide.common.api.IAttributeInfo; 24 import com.android.ide.common.api.IAttributeInfo.Format; 25 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 26 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 27 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; 28 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; 29 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; 30 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; 31 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; 32 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; 33 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; 34 import com.android.tools.lint.detector.api.LintUtils; 35 import com.google.common.collect.ArrayListMultimap; 36 import com.google.common.collect.Lists; 37 import com.google.common.collect.Maps; 38 import com.google.common.collect.Multimap; 39 40 import org.eclipse.jface.dialogs.MessageDialog; 41 import org.eclipse.swt.SWT; 42 import org.eclipse.swt.events.SelectionAdapter; 43 import org.eclipse.swt.events.SelectionEvent; 44 import org.eclipse.swt.layout.GridData; 45 import org.eclipse.swt.layout.GridLayout; 46 import org.eclipse.swt.widgets.Composite; 47 import org.eclipse.swt.widgets.Label; 48 import org.eclipse.swt.widgets.Link; 49 import org.eclipse.ui.IWorkbench; 50 import org.eclipse.ui.PlatformUI; 51 import org.eclipse.ui.browser.IWebBrowser; 52 import org.eclipse.wb.internal.core.editor.structure.property.PropertyListIntersector; 53 import org.eclipse.wb.internal.core.model.property.ComplexProperty; 54 import org.eclipse.wb.internal.core.model.property.Property; 55 import org.eclipse.wb.internal.core.model.property.category.PropertyCategory; 56 import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; 57 import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation; 58 59 import java.net.URL; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Collection; 63 import java.util.Collections; 64 import java.util.EnumSet; 65 import java.util.HashMap; 66 import java.util.HashSet; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Set; 70 import java.util.WeakHashMap; 71 72 /** 73 * The {@link PropertyFactory} creates (and caches) the set of {@link Property} 74 * instances applicable to a given node. It's also responsible for ordering 75 * these, and sometimes combining them into {@link ComplexProperty} category 76 * nodes. 77 * <p> 78 * TODO: For any properties that are *set* in XML, they should NOT be labeled as 79 * advanced (which would make them disappear) 80 */ 81 public class PropertyFactory { 82 /** Disable cache during development only */ 83 @SuppressWarnings("unused") 84 private static final boolean CACHE_ENABLED = true || !LintUtils.assertionsEnabled(); 85 static { 86 if (!CACHE_ENABLED) { 87 System.err.println("WARNING: The property cache is disabled"); 88 } 89 } 90 91 private static final Property[] NO_PROPERTIES = new Property[0]; 92 93 private static final int PRIO_FIRST = -100000; 94 private static final int PRIO_SECOND = PRIO_FIRST + 10; 95 private static final int PRIO_LAST = 100000; 96 97 private final GraphicalEditorPart mGraphicalEditorPart; 98 private Map<UiViewElementNode, Property[]> mCache = 99 new WeakHashMap<UiViewElementNode, Property[]>(); 100 private UiViewElementNode mCurrentViewCookie; 101 102 /** Sorting orders for the properties */ 103 public enum SortingMode { 104 NATURAL, 105 BY_ORIGIN, 106 ALPHABETICAL; 107 } 108 109 /** The default sorting mode */ 110 public static final SortingMode DEFAULT_MODE = SortingMode.BY_ORIGIN; 111 112 private SortingMode mSortMode = DEFAULT_MODE; 113 private SortingMode mCacheSortMode; 114 PropertyFactory(GraphicalEditorPart graphicalEditorPart)115 public PropertyFactory(GraphicalEditorPart graphicalEditorPart) { 116 mGraphicalEditorPart = graphicalEditorPart; 117 } 118 119 /** 120 * Get the properties for the given list of selection items. 121 * 122 * @param items the {@link CanvasViewInfo} instances to get an intersected 123 * property list for 124 * @return the properties for the given items 125 */ getProperties(List<CanvasViewInfo> items)126 public Property[] getProperties(List<CanvasViewInfo> items) { 127 mCurrentViewCookie = null; 128 129 if (items == null || items.size() == 0) { 130 return NO_PROPERTIES; 131 } else if (items.size() == 1) { 132 CanvasViewInfo item = items.get(0); 133 mCurrentViewCookie = item.getUiViewNode(); 134 135 return getProperties(item); 136 } else { 137 // intersect properties 138 PropertyListIntersector intersector = new PropertyListIntersector(); 139 for (CanvasViewInfo node : items) { 140 intersector.intersect(getProperties(node)); 141 } 142 143 return intersector.getProperties(); 144 } 145 } 146 getProperties(CanvasViewInfo item)147 private Property[] getProperties(CanvasViewInfo item) { 148 UiViewElementNode node = item.getUiViewNode(); 149 if (node == null) { 150 return NO_PROPERTIES; 151 } 152 153 if (mCacheSortMode != mSortMode) { 154 mCacheSortMode = mSortMode; 155 mCache.clear(); 156 } 157 158 Property[] properties = mCache.get(node); 159 if (!CACHE_ENABLED) { 160 properties = null; 161 } 162 if (properties == null) { 163 Collection<? extends Property> propertyList = getProperties(node); 164 if (propertyList == null) { 165 properties = new Property[0]; 166 } else { 167 properties = propertyList.toArray(new Property[propertyList.size()]); 168 } 169 mCache.put(node, properties); 170 } 171 return properties; 172 } 173 174 getProperties(UiViewElementNode node)175 protected Collection<? extends Property> getProperties(UiViewElementNode node) { 176 ViewMetadataRepository repository = ViewMetadataRepository.get(); 177 ViewElementDescriptor viewDescriptor = (ViewElementDescriptor) node.getDescriptor(); 178 String fqcn = viewDescriptor.getFullClassName(); 179 Set<String> top = new HashSet<String>(repository.getTopAttributes(fqcn)); 180 AttributeDescriptor[] attributeDescriptors = node.getAttributeDescriptors(); 181 182 List<XmlProperty> properties = new ArrayList<XmlProperty>(attributeDescriptors.length); 183 int priority = 0; 184 for (final AttributeDescriptor descriptor : attributeDescriptors) { 185 // TODO: Filter out non-public properties!! 186 // (They shouldn't be in the descriptors at all) 187 188 assert !(descriptor instanceof SeparatorAttributeDescriptor); // No longer inserted 189 if (descriptor instanceof XmlnsAttributeDescriptor) { 190 continue; 191 } 192 193 PropertyEditor editor = XmlPropertyEditor.INSTANCE; 194 IAttributeInfo info = descriptor.getAttributeInfo(); 195 if (info != null) { 196 EnumSet<Format> formats = info.getFormats(); 197 if (formats.contains(Format.BOOLEAN)) { 198 editor = BooleanXmlPropertyEditor.INSTANCE; 199 } else if (formats.contains(Format.ENUM)) { 200 // We deliberately don't use EnumXmlPropertyEditor.INSTANCE here, 201 // since some attributes (such as layout_width) can have not just one 202 // of the enum values but custom values such as "42dp" as well. And 203 // furthermore, we don't even bother limiting this to formats.size()==1, 204 // since the editing experience with the enum property editor is 205 // more limited than the text editor plus enum completer anyway 206 // (for example, you can't type to filter the values, and clearing 207 // the value is harder.) 208 } 209 } 210 211 XmlProperty property = new XmlProperty(editor, this, node, descriptor); 212 // Assign ids sequentially. This ensures that the properties will mostly keep their 213 // relative order (such as placing width before height), even though we will regroup 214 // some (such as properties in the same category, and the layout params etc) 215 priority += 10; 216 217 PropertyCategory category = PropertyCategory.NORMAL; 218 String name = descriptor.getXmlLocalName(); 219 if (top.contains(name) || PropertyMetadata.isPreferred(name)) { 220 category = PropertyCategory.PREFERRED; 221 property.setPriority(PRIO_FIRST + priority); 222 } else { 223 property.setPriority(priority); 224 225 // Prefer attributes defined on the specific type of this 226 // widget 227 // NOTE: This doesn't work very well for TextViews 228 /* IAttributeInfo attributeInfo = descriptor.getAttributeInfo(); 229 if (attributeInfo != null && fqcn.equals(attributeInfo.getDefinedBy())) { 230 category = PropertyCategory.PREFERRED; 231 } else*/ if (PropertyMetadata.isAdvanced(name)) { 232 category = PropertyCategory.ADVANCED; 233 } 234 } 235 if (category != null) { 236 property.setCategory(category); 237 } 238 properties.add(property); 239 } 240 241 switch (mSortMode) { 242 case BY_ORIGIN: 243 return sortByOrigin(node, properties); 244 245 case ALPHABETICAL: 246 return sortAlphabetically(node, properties); 247 248 default: 249 case NATURAL: 250 return sortNatural(node, properties); 251 } 252 } 253 sortAlphabetically( UiViewElementNode node, List<XmlProperty> properties)254 protected Collection<? extends Property> sortAlphabetically( 255 UiViewElementNode node, 256 List<XmlProperty> properties) { 257 Collections.sort(properties, Property.ALPHABETICAL); 258 return properties; 259 } 260 sortByOrigin( UiViewElementNode node, List<XmlProperty> properties)261 protected Collection<? extends Property> sortByOrigin( 262 UiViewElementNode node, 263 List<XmlProperty> properties) { 264 List<Property> collapsed = new ArrayList<Property>(properties.size()); 265 List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20); 266 List<Property> marginProperties = null; 267 List<Property> deprecatedProperties = null; 268 Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>(); 269 Multimap<String, Property> categoryToProperties = ArrayListMultimap.create(); 270 271 if (properties.isEmpty()) { 272 return properties; 273 } 274 275 ViewElementDescriptor parent = (ViewElementDescriptor) properties.get(0).getDescriptor() 276 .getParent(); 277 Map<String, Integer> categoryPriorities = Maps.newHashMap(); 278 int nextCategoryPriority = 100; 279 while (parent != null) { 280 categoryPriorities.put(parent.getFullClassName(), nextCategoryPriority += 100); 281 parent = parent.getSuperClassDesc(); 282 } 283 284 for (int i = 0, max = properties.size(); i < max; i++) { 285 XmlProperty property = properties.get(i); 286 287 AttributeDescriptor descriptor = property.getDescriptor(); 288 if (descriptor.isDeprecated()) { 289 if (deprecatedProperties == null) { 290 deprecatedProperties = Lists.newArrayListWithExpectedSize(10); 291 } 292 deprecatedProperties.add(property); 293 continue; 294 } 295 296 String firstName = descriptor.getXmlLocalName(); 297 if (firstName.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { 298 if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) { 299 if (marginProperties == null) { 300 marginProperties = Lists.newArrayListWithExpectedSize(5); 301 } 302 marginProperties.add(property); 303 } else { 304 layoutProperties.add(property); 305 } 306 continue; 307 } 308 309 if (firstName.equals(ATTR_ID)) { 310 // Add id to the front (though the layout parameters will be added to 311 // the front of this at the end) 312 property.setPriority(PRIO_FIRST); 313 collapsed.add(property); 314 continue; 315 } 316 317 if (property.getCategory() == PropertyCategory.PREFERRED) { 318 collapsed.add(property); 319 // Fall through: these are *duplicated* inside their defining categories! 320 // However, create a new instance of the property, such that the propertysheet 321 // doesn't see the same property instance twice (when selected, it will highlight 322 // both, etc.) Also, set the category to Normal such that we don't draw attention 323 // to it again. We want it to appear in both places such that somebody looking 324 // within a category will always find it there, even if for this specific 325 // view type it's a common attribute and replicated up at the top. 326 XmlProperty oldProperty = property; 327 property = new XmlProperty(oldProperty.getEditor(), this, node, 328 oldProperty.getDescriptor()); 329 property.setPriority(oldProperty.getPriority()); 330 } 331 332 IAttributeInfo attributeInfo = descriptor.getAttributeInfo(); 333 if (attributeInfo != null && attributeInfo.getDefinedBy() != null) { 334 String category = attributeInfo.getDefinedBy(); 335 ComplexProperty complex = categoryToProperty.get(category); 336 if (complex == null) { 337 complex = new ComplexProperty( 338 category.substring(category.lastIndexOf('.') + 1), 339 "[]", 340 null /* properties */); 341 categoryToProperty.put(category, complex); 342 Integer categoryPriority = categoryPriorities.get(category); 343 if (categoryPriority != null) { 344 complex.setPriority(categoryPriority); 345 } else { 346 // Descriptor for an attribute whose definedBy does *not* 347 // correspond to one of the known superclasses of this widget. 348 // This sometimes happens; for example, a RatingBar will pull in 349 // an ImageView's minWidth attribute. Probably an error in the 350 // metadata, but deal with it gracefully here. 351 categoryPriorities.put(category, nextCategoryPriority += 100); 352 complex.setPriority(nextCategoryPriority); 353 } 354 } 355 categoryToProperties.put(category, property); 356 continue; 357 } else { 358 collapsed.add(property); 359 } 360 } 361 362 // Update the complex properties 363 for (String category : categoryToProperties.keySet()) { 364 Collection<Property> subProperties = categoryToProperties.get(category); 365 if (subProperties.size() > 1) { 366 ComplexProperty complex = categoryToProperty.get(category); 367 assert complex != null : category; 368 Property[] subArray = new Property[subProperties.size()]; 369 complex.setProperties(subProperties.toArray(subArray)); 370 //complex.setPriority(subArray[0].getPriority()); 371 372 collapsed.add(complex); 373 374 boolean allAdvanced = true; 375 boolean isPreferred = false; 376 for (Property p : subProperties) { 377 PropertyCategory c = p.getCategory(); 378 if (c != PropertyCategory.ADVANCED) { 379 allAdvanced = false; 380 } 381 if (c == PropertyCategory.PREFERRED) { 382 isPreferred = true; 383 } 384 } 385 if (isPreferred) { 386 complex.setCategory(PropertyCategory.PREFERRED); 387 } else if (allAdvanced) { 388 complex.setCategory(PropertyCategory.ADVANCED); 389 } 390 } else if (subProperties.size() == 1) { 391 collapsed.add(subProperties.iterator().next()); 392 } 393 } 394 395 if (layoutProperties.size() > 0 || marginProperties != null) { 396 if (marginProperties != null) { 397 XmlProperty[] m = 398 marginProperties.toArray(new XmlProperty[marginProperties.size()]); 399 Property marginProperty = new ComplexProperty( 400 "Margins", 401 "[]", 402 m); 403 layoutProperties.add(marginProperty); 404 marginProperty.setPriority(PRIO_LAST); 405 406 for (XmlProperty p : m) { 407 p.setParent(marginProperty); 408 } 409 } 410 Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]); 411 Arrays.sort(l, Property.PRIORITY); 412 Property property = new ComplexProperty( 413 "Layout Parameters", 414 "[]", 415 l); 416 for (Property p : l) { 417 if (p instanceof XmlProperty) { 418 ((XmlProperty) p).setParent(property); 419 } 420 } 421 property.setCategory(PropertyCategory.PREFERRED); 422 collapsed.add(property); 423 property.setPriority(PRIO_SECOND); 424 } 425 426 if (deprecatedProperties != null && deprecatedProperties.size() > 0) { 427 Property property = new ComplexProperty( 428 "Deprecated", 429 "(Deprecated Properties)", 430 deprecatedProperties.toArray(new Property[deprecatedProperties.size()])); 431 property.setPriority(PRIO_LAST); 432 collapsed.add(property); 433 } 434 435 Collections.sort(collapsed, Property.PRIORITY); 436 437 return collapsed; 438 } 439 sortNatural( UiViewElementNode node, List<XmlProperty> properties)440 protected Collection<? extends Property> sortNatural( 441 UiViewElementNode node, 442 List<XmlProperty> properties) { 443 Collections.sort(properties, Property.ALPHABETICAL); 444 List<Property> collapsed = new ArrayList<Property>(properties.size()); 445 List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20); 446 List<Property> marginProperties = null; 447 List<Property> deprecatedProperties = null; 448 Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>(); 449 Multimap<String, Property> categoryToProperties = ArrayListMultimap.create(); 450 451 for (int i = 0, max = properties.size(); i < max; i++) { 452 XmlProperty property = properties.get(i); 453 454 AttributeDescriptor descriptor = property.getDescriptor(); 455 if (descriptor.isDeprecated()) { 456 if (deprecatedProperties == null) { 457 deprecatedProperties = Lists.newArrayListWithExpectedSize(10); 458 } 459 deprecatedProperties.add(property); 460 continue; 461 } 462 463 String firstName = descriptor.getXmlLocalName(); 464 if (firstName.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { 465 if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) { 466 if (marginProperties == null) { 467 marginProperties = Lists.newArrayListWithExpectedSize(5); 468 } 469 marginProperties.add(property); 470 } else { 471 layoutProperties.add(property); 472 } 473 continue; 474 } 475 476 if (firstName.equals(ATTR_ID)) { 477 // Add id to the front (though the layout parameters will be added to 478 // the front of this at the end) 479 property.setPriority(PRIO_FIRST); 480 collapsed.add(property); 481 continue; 482 } 483 484 String category = PropertyMetadata.getCategory(firstName); 485 if (category != null) { 486 ComplexProperty complex = categoryToProperty.get(category); 487 if (complex == null) { 488 complex = new ComplexProperty( 489 category, 490 "[]", 491 null /* properties */); 492 categoryToProperty.put(category, complex); 493 complex.setPriority(property.getPriority()); 494 } 495 categoryToProperties.put(category, property); 496 continue; 497 } 498 499 // Index of second word in the first name, so in fooBar it's 3 (index of 'B') 500 int firstNameIndex = firstName.length(); 501 for (int k = 0, kn = firstName.length(); k < kn; k++) { 502 if (Character.isUpperCase(firstName.charAt(k))) { 503 firstNameIndex = k; 504 break; 505 } 506 } 507 508 // Scout forwards and see how many properties we can combine 509 int j = i + 1; 510 if (property.getCategory() != PropertyCategory.PREFERRED 511 && !property.getDescriptor().isDeprecated()) { 512 for (; j < max; j++) { 513 XmlProperty next = properties.get(j); 514 String nextName = next.getName(); 515 if (nextName.regionMatches(0, firstName, 0, firstNameIndex) 516 // Also make sure we begin the second word at the next 517 // character; if not, we could have something like 518 // scrollBar 519 // scrollingBehavior 520 && nextName.length() > firstNameIndex 521 && Character.isUpperCase(nextName.charAt(firstNameIndex))) { 522 523 // Deprecated attributes, and preferred attributes, should not 524 // be pushed into normal clusters (preferred stay top-level 525 // and sort to the top, deprecated are all put in the same cluster at 526 // the end) 527 528 if (next.getCategory() == PropertyCategory.PREFERRED) { 529 break; 530 } 531 if (next.getDescriptor().isDeprecated()) { 532 break; 533 } 534 535 // This property should be combined with the previous 536 // property 537 } else { 538 break; 539 } 540 } 541 } 542 if (j - i > 1) { 543 // Combining multiple properties: all the properties from i 544 // through j inclusive 545 XmlProperty[] subprops = new XmlProperty[j - i]; 546 for (int k = i, index = 0; k < j; k++, index++) { 547 subprops[index] = properties.get(k); 548 } 549 Arrays.sort(subprops, Property.PRIORITY); 550 551 // See if we can compute a LONGER base than just the first word. 552 // For example, if we have "lineSpacingExtra" and "lineSpacingMultiplier" 553 // we'd like the base to be "lineSpacing", not "line". 554 int common = firstNameIndex; 555 for (int k = firstNameIndex + 1, n = firstName.length(); k < n; k++) { 556 if (Character.isUpperCase(firstName.charAt(k))) { 557 common = k; 558 break; 559 } 560 } 561 if (common > firstNameIndex) { 562 for (int k = 0, n = subprops.length; k < n; k++) { 563 String nextName = subprops[k].getName(); 564 if (nextName.regionMatches(0, firstName, 0, common) 565 // Also make sure we begin the second word at the next 566 // character; if not, we could have something like 567 // scrollBar 568 // scrollingBehavior 569 && nextName.length() > common 570 && Character.isUpperCase(nextName.charAt(common))) { 571 // New prefix is okay 572 } else { 573 common = firstNameIndex; 574 break; 575 } 576 } 577 firstNameIndex = common; 578 } 579 580 String base = firstName.substring(0, firstNameIndex); 581 base = DescriptorsUtils.capitalize(base); 582 Property complexProperty = new ComplexProperty( 583 base, 584 "[]", 585 subprops); 586 complexProperty.setPriority(subprops[0].getPriority()); 587 //complexProperty.setCategory(PropertyCategory.PREFERRED); 588 collapsed.add(complexProperty); 589 boolean allAdvanced = true; 590 boolean isPreferred = false; 591 for (XmlProperty p : subprops) { 592 p.setParent(complexProperty); 593 PropertyCategory c = p.getCategory(); 594 if (c != PropertyCategory.ADVANCED) { 595 allAdvanced = false; 596 } 597 if (c == PropertyCategory.PREFERRED) { 598 isPreferred = true; 599 } 600 } 601 if (isPreferred) { 602 complexProperty.setCategory(PropertyCategory.PREFERRED); 603 } else if (allAdvanced) { 604 complexProperty.setCategory(PropertyCategory.PREFERRED); 605 } 606 } else { 607 // Add the individual properties (usually 1, sometimes 2 608 for (int k = i; k < j; k++) { 609 collapsed.add(properties.get(k)); 610 } 611 } 612 613 i = j - 1; // -1: compensate in advance for the for-loop adding 1 614 } 615 616 // Update the complex properties 617 for (String category : categoryToProperties.keySet()) { 618 Collection<Property> subProperties = categoryToProperties.get(category); 619 if (subProperties.size() > 1) { 620 ComplexProperty complex = categoryToProperty.get(category); 621 assert complex != null : category; 622 Property[] subArray = new Property[subProperties.size()]; 623 complex.setProperties(subProperties.toArray(subArray)); 624 complex.setPriority(subArray[0].getPriority()); 625 collapsed.add(complex); 626 627 boolean allAdvanced = true; 628 boolean isPreferred = false; 629 for (Property p : subProperties) { 630 PropertyCategory c = p.getCategory(); 631 if (c != PropertyCategory.ADVANCED) { 632 allAdvanced = false; 633 } 634 if (c == PropertyCategory.PREFERRED) { 635 isPreferred = true; 636 } 637 } 638 if (isPreferred) { 639 complex.setCategory(PropertyCategory.PREFERRED); 640 } else if (allAdvanced) { 641 complex.setCategory(PropertyCategory.ADVANCED); 642 } 643 } else if (subProperties.size() == 1) { 644 collapsed.add(subProperties.iterator().next()); 645 } 646 } 647 648 if (layoutProperties.size() > 0 || marginProperties != null) { 649 if (marginProperties != null) { 650 XmlProperty[] m = 651 marginProperties.toArray(new XmlProperty[marginProperties.size()]); 652 Property marginProperty = new ComplexProperty( 653 "Margins", 654 "[]", 655 m); 656 layoutProperties.add(marginProperty); 657 marginProperty.setPriority(PRIO_LAST); 658 659 for (XmlProperty p : m) { 660 p.setParent(marginProperty); 661 } 662 } 663 Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]); 664 Arrays.sort(l, Property.PRIORITY); 665 Property property = new ComplexProperty( 666 "Layout Parameters", 667 "[]", 668 l); 669 for (Property p : l) { 670 if (p instanceof XmlProperty) { 671 ((XmlProperty) p).setParent(property); 672 } 673 } 674 property.setCategory(PropertyCategory.PREFERRED); 675 collapsed.add(property); 676 property.setPriority(PRIO_SECOND); 677 } 678 679 if (deprecatedProperties != null && deprecatedProperties.size() > 0) { 680 Property property = new ComplexProperty( 681 "Deprecated", 682 "(Deprecated Properties)", 683 deprecatedProperties.toArray(new Property[deprecatedProperties.size()])); 684 property.setPriority(PRIO_LAST); 685 collapsed.add(property); 686 } 687 688 Collections.sort(collapsed, Property.PRIORITY); 689 690 return collapsed; 691 } 692 693 @Nullable getGraphicalEditor()694 GraphicalEditorPart getGraphicalEditor() { 695 return mGraphicalEditorPart; 696 } 697 698 // HACK: This should be passed into each property instead getCurrentViewObject()699 public Object getCurrentViewObject() { 700 return mCurrentViewCookie; 701 } 702 setSortingMode(SortingMode sortingMode)703 public void setSortingMode(SortingMode sortingMode) { 704 mSortMode = sortingMode; 705 } 706 707 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574 addWorkaround(Composite parent)708 public static Composite addWorkaround(Composite parent) { 709 if (ButtonPropertyEditorPresentation.isInWorkaround) { 710 Composite top = new Composite(parent, SWT.NONE); 711 top.setLayout(new GridLayout(1, false)); 712 Label label = new Label(top, SWT.WRAP); 713 label.setText( 714 "This dialog is shown instead of an inline text editor as a\n" + 715 "workaround for an Eclipse bug specific to OSX Mountain Lion.\n" + 716 "It should be fixed in Eclipse 4.3."); 717 label.setForeground(top.getDisplay().getSystemColor(SWT.COLOR_RED)); 718 GridData data = new GridData(); 719 data.grabExcessVerticalSpace = false; 720 data.grabExcessHorizontalSpace = false; 721 data.horizontalAlignment = GridData.FILL; 722 data.verticalAlignment = GridData.BEGINNING; 723 label.setLayoutData(data); 724 725 Link link = new Link(top, SWT.NO_FOCUS); 726 link.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); 727 link.setText("<a>https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574</a>"); 728 link.addSelectionListener(new SelectionAdapter() { 729 @Override 730 public void widgetSelected(SelectionEvent event) { 731 try { 732 IWorkbench workbench = PlatformUI.getWorkbench(); 733 IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser(); 734 browser.openURL(new URL(event.text)); 735 } catch (Exception e) { 736 String message = String.format( 737 "Could not open browser. Vist\n%1$s\ninstead.", 738 event.text); 739 MessageDialog.openError(((Link)event.getSource()).getShell(), 740 "Browser Error", message); 741 } 742 } 743 }); 744 745 return top; 746 } 747 748 return null; 749 } 750 } 751