Home | History | Annotate | Download | only in properties
      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 
    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      */
    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 
    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 
    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 
    254     protected Collection<? extends Property> sortAlphabetically(
    255             UiViewElementNode node,
    256             List<XmlProperty> properties) {
    257         Collections.sort(properties, Property.ALPHABETICAL);
    258         return properties;
    259     }
    260 
    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 
    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
    694     GraphicalEditorPart getGraphicalEditor() {
    695         return mGraphicalEditorPart;
    696     }
    697 
    698     // HACK: This should be passed into each property instead
    699     public Object getCurrentViewObject() {
    700         return mCurrentViewCookie;
    701     }
    702 
    703     public void setSortingMode(SortingMode sortingMode) {
    704         mSortMode = sortingMode;
    705     }
    706 
    707     // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574
    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