aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java750
1 files changed, 750 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
new file mode 100644
index 000000000..2b8cfbf43
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+import org.eclipse.wb.internal.core.editor.structure.property.PropertyListIntersector;
+import org.eclipse.wb.internal.core.model.property.ComplexProperty;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * The {@link PropertyFactory} creates (and caches) the set of {@link Property}
+ * instances applicable to a given node. It's also responsible for ordering
+ * these, and sometimes combining them into {@link ComplexProperty} category
+ * nodes.
+ * <p>
+ * TODO: For any properties that are *set* in XML, they should NOT be labeled as
+ * advanced (which would make them disappear)
+ */
+public class PropertyFactory {
+ /** Disable cache during development only */
+ @SuppressWarnings("unused")
+ private static final boolean CACHE_ENABLED = true || !LintUtils.assertionsEnabled();
+ static {
+ if (!CACHE_ENABLED) {
+ System.err.println("WARNING: The property cache is disabled");
+ }
+ }
+
+ private static final Property[] NO_PROPERTIES = new Property[0];
+
+ private static final int PRIO_FIRST = -100000;
+ private static final int PRIO_SECOND = PRIO_FIRST + 10;
+ private static final int PRIO_LAST = 100000;
+
+ private final GraphicalEditorPart mGraphicalEditorPart;
+ private Map<UiViewElementNode, Property[]> mCache =
+ new WeakHashMap<UiViewElementNode, Property[]>();
+ private UiViewElementNode mCurrentViewCookie;
+
+ /** Sorting orders for the properties */
+ public enum SortingMode {
+ NATURAL,
+ BY_ORIGIN,
+ ALPHABETICAL;
+ }
+
+ /** The default sorting mode */
+ public static final SortingMode DEFAULT_MODE = SortingMode.BY_ORIGIN;
+
+ private SortingMode mSortMode = DEFAULT_MODE;
+ private SortingMode mCacheSortMode;
+
+ public PropertyFactory(GraphicalEditorPart graphicalEditorPart) {
+ mGraphicalEditorPart = graphicalEditorPart;
+ }
+
+ /**
+ * Get the properties for the given list of selection items.
+ *
+ * @param items the {@link CanvasViewInfo} instances to get an intersected
+ * property list for
+ * @return the properties for the given items
+ */
+ public Property[] getProperties(List<CanvasViewInfo> items) {
+ mCurrentViewCookie = null;
+
+ if (items == null || items.size() == 0) {
+ return NO_PROPERTIES;
+ } else if (items.size() == 1) {
+ CanvasViewInfo item = items.get(0);
+ mCurrentViewCookie = item.getUiViewNode();
+
+ return getProperties(item);
+ } else {
+ // intersect properties
+ PropertyListIntersector intersector = new PropertyListIntersector();
+ for (CanvasViewInfo node : items) {
+ intersector.intersect(getProperties(node));
+ }
+
+ return intersector.getProperties();
+ }
+ }
+
+ private Property[] getProperties(CanvasViewInfo item) {
+ UiViewElementNode node = item.getUiViewNode();
+ if (node == null) {
+ return NO_PROPERTIES;
+ }
+
+ if (mCacheSortMode != mSortMode) {
+ mCacheSortMode = mSortMode;
+ mCache.clear();
+ }
+
+ Property[] properties = mCache.get(node);
+ if (!CACHE_ENABLED) {
+ properties = null;
+ }
+ if (properties == null) {
+ Collection<? extends Property> propertyList = getProperties(node);
+ if (propertyList == null) {
+ properties = new Property[0];
+ } else {
+ properties = propertyList.toArray(new Property[propertyList.size()]);
+ }
+ mCache.put(node, properties);
+ }
+ return properties;
+ }
+
+
+ protected Collection<? extends Property> getProperties(UiViewElementNode node) {
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ ViewElementDescriptor viewDescriptor = (ViewElementDescriptor) node.getDescriptor();
+ String fqcn = viewDescriptor.getFullClassName();
+ Set<String> top = new HashSet<String>(repository.getTopAttributes(fqcn));
+ AttributeDescriptor[] attributeDescriptors = node.getAttributeDescriptors();
+
+ List<XmlProperty> properties = new ArrayList<XmlProperty>(attributeDescriptors.length);
+ int priority = 0;
+ for (final AttributeDescriptor descriptor : attributeDescriptors) {
+ // TODO: Filter out non-public properties!!
+ // (They shouldn't be in the descriptors at all)
+
+ assert !(descriptor instanceof SeparatorAttributeDescriptor); // No longer inserted
+ if (descriptor instanceof XmlnsAttributeDescriptor) {
+ continue;
+ }
+
+ PropertyEditor editor = XmlPropertyEditor.INSTANCE;
+ IAttributeInfo info = descriptor.getAttributeInfo();
+ if (info != null) {
+ EnumSet<Format> formats = info.getFormats();
+ if (formats.contains(Format.BOOLEAN)) {
+ editor = BooleanXmlPropertyEditor.INSTANCE;
+ } else if (formats.contains(Format.ENUM)) {
+ // We deliberately don't use EnumXmlPropertyEditor.INSTANCE here,
+ // since some attributes (such as layout_width) can have not just one
+ // of the enum values but custom values such as "42dp" as well. And
+ // furthermore, we don't even bother limiting this to formats.size()==1,
+ // since the editing experience with the enum property editor is
+ // more limited than the text editor plus enum completer anyway
+ // (for example, you can't type to filter the values, and clearing
+ // the value is harder.)
+ }
+ }
+
+ XmlProperty property = new XmlProperty(editor, this, node, descriptor);
+ // Assign ids sequentially. This ensures that the properties will mostly keep their
+ // relative order (such as placing width before height), even though we will regroup
+ // some (such as properties in the same category, and the layout params etc)
+ priority += 10;
+
+ PropertyCategory category = PropertyCategory.NORMAL;
+ String name = descriptor.getXmlLocalName();
+ if (top.contains(name) || PropertyMetadata.isPreferred(name)) {
+ category = PropertyCategory.PREFERRED;
+ property.setPriority(PRIO_FIRST + priority);
+ } else {
+ property.setPriority(priority);
+
+ // Prefer attributes defined on the specific type of this
+ // widget
+ // NOTE: This doesn't work very well for TextViews
+ /* IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
+ if (attributeInfo != null && fqcn.equals(attributeInfo.getDefinedBy())) {
+ category = PropertyCategory.PREFERRED;
+ } else*/ if (PropertyMetadata.isAdvanced(name)) {
+ category = PropertyCategory.ADVANCED;
+ }
+ }
+ if (category != null) {
+ property.setCategory(category);
+ }
+ properties.add(property);
+ }
+
+ switch (mSortMode) {
+ case BY_ORIGIN:
+ return sortByOrigin(node, properties);
+
+ case ALPHABETICAL:
+ return sortAlphabetically(node, properties);
+
+ default:
+ case NATURAL:
+ return sortNatural(node, properties);
+ }
+ }
+
+ protected Collection<? extends Property> sortAlphabetically(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ Collections.sort(properties, Property.ALPHABETICAL);
+ return properties;
+ }
+
+ protected Collection<? extends Property> sortByOrigin(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ List<Property> collapsed = new ArrayList<Property>(properties.size());
+ List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20);
+ List<Property> marginProperties = null;
+ List<Property> deprecatedProperties = null;
+ Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>();
+ Multimap<String, Property> categoryToProperties = ArrayListMultimap.create();
+
+ if (properties.isEmpty()) {
+ return properties;
+ }
+
+ ViewElementDescriptor parent = (ViewElementDescriptor) properties.get(0).getDescriptor()
+ .getParent();
+ Map<String, Integer> categoryPriorities = Maps.newHashMap();
+ int nextCategoryPriority = 100;
+ while (parent != null) {
+ categoryPriorities.put(parent.getFullClassName(), nextCategoryPriority += 100);
+ parent = parent.getSuperClassDesc();
+ }
+
+ for (int i = 0, max = properties.size(); i < max; i++) {
+ XmlProperty property = properties.get(i);
+
+ AttributeDescriptor descriptor = property.getDescriptor();
+ if (descriptor.isDeprecated()) {
+ if (deprecatedProperties == null) {
+ deprecatedProperties = Lists.newArrayListWithExpectedSize(10);
+ }
+ deprecatedProperties.add(property);
+ continue;
+ }
+
+ String firstName = descriptor.getXmlLocalName();
+ if (firstName.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) {
+ if (marginProperties == null) {
+ marginProperties = Lists.newArrayListWithExpectedSize(5);
+ }
+ marginProperties.add(property);
+ } else {
+ layoutProperties.add(property);
+ }
+ continue;
+ }
+
+ if (firstName.equals(ATTR_ID)) {
+ // Add id to the front (though the layout parameters will be added to
+ // the front of this at the end)
+ property.setPriority(PRIO_FIRST);
+ collapsed.add(property);
+ continue;
+ }
+
+ if (property.getCategory() == PropertyCategory.PREFERRED) {
+ collapsed.add(property);
+ // Fall through: these are *duplicated* inside their defining categories!
+ // However, create a new instance of the property, such that the propertysheet
+ // doesn't see the same property instance twice (when selected, it will highlight
+ // both, etc.) Also, set the category to Normal such that we don't draw attention
+ // to it again. We want it to appear in both places such that somebody looking
+ // within a category will always find it there, even if for this specific
+ // view type it's a common attribute and replicated up at the top.
+ XmlProperty oldProperty = property;
+ property = new XmlProperty(oldProperty.getEditor(), this, node,
+ oldProperty.getDescriptor());
+ property.setPriority(oldProperty.getPriority());
+ }
+
+ IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
+ if (attributeInfo != null && attributeInfo.getDefinedBy() != null) {
+ String category = attributeInfo.getDefinedBy();
+ ComplexProperty complex = categoryToProperty.get(category);
+ if (complex == null) {
+ complex = new ComplexProperty(
+ category.substring(category.lastIndexOf('.') + 1),
+ "[]",
+ null /* properties */);
+ categoryToProperty.put(category, complex);
+ Integer categoryPriority = categoryPriorities.get(category);
+ if (categoryPriority != null) {
+ complex.setPriority(categoryPriority);
+ } else {
+ // Descriptor for an attribute whose definedBy does *not*
+ // correspond to one of the known superclasses of this widget.
+ // This sometimes happens; for example, a RatingBar will pull in
+ // an ImageView's minWidth attribute. Probably an error in the
+ // metadata, but deal with it gracefully here.
+ categoryPriorities.put(category, nextCategoryPriority += 100);
+ complex.setPriority(nextCategoryPriority);
+ }
+ }
+ categoryToProperties.put(category, property);
+ continue;
+ } else {
+ collapsed.add(property);
+ }
+ }
+
+ // Update the complex properties
+ for (String category : categoryToProperties.keySet()) {
+ Collection<Property> subProperties = categoryToProperties.get(category);
+ if (subProperties.size() > 1) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ assert complex != null : category;
+ Property[] subArray = new Property[subProperties.size()];
+ complex.setProperties(subProperties.toArray(subArray));
+ //complex.setPriority(subArray[0].getPriority());
+
+ collapsed.add(complex);
+
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (Property p : subProperties) {
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complex.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complex.setCategory(PropertyCategory.ADVANCED);
+ }
+ } else if (subProperties.size() == 1) {
+ collapsed.add(subProperties.iterator().next());
+ }
+ }
+
+ if (layoutProperties.size() > 0 || marginProperties != null) {
+ if (marginProperties != null) {
+ XmlProperty[] m =
+ marginProperties.toArray(new XmlProperty[marginProperties.size()]);
+ Property marginProperty = new ComplexProperty(
+ "Margins",
+ "[]",
+ m);
+ layoutProperties.add(marginProperty);
+ marginProperty.setPriority(PRIO_LAST);
+
+ for (XmlProperty p : m) {
+ p.setParent(marginProperty);
+ }
+ }
+ Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]);
+ Arrays.sort(l, Property.PRIORITY);
+ Property property = new ComplexProperty(
+ "Layout Parameters",
+ "[]",
+ l);
+ for (Property p : l) {
+ if (p instanceof XmlProperty) {
+ ((XmlProperty) p).setParent(property);
+ }
+ }
+ property.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(property);
+ property.setPriority(PRIO_SECOND);
+ }
+
+ if (deprecatedProperties != null && deprecatedProperties.size() > 0) {
+ Property property = new ComplexProperty(
+ "Deprecated",
+ "(Deprecated Properties)",
+ deprecatedProperties.toArray(new Property[deprecatedProperties.size()]));
+ property.setPriority(PRIO_LAST);
+ collapsed.add(property);
+ }
+
+ Collections.sort(collapsed, Property.PRIORITY);
+
+ return collapsed;
+ }
+
+ protected Collection<? extends Property> sortNatural(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ Collections.sort(properties, Property.ALPHABETICAL);
+ List<Property> collapsed = new ArrayList<Property>(properties.size());
+ List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20);
+ List<Property> marginProperties = null;
+ List<Property> deprecatedProperties = null;
+ Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>();
+ Multimap<String, Property> categoryToProperties = ArrayListMultimap.create();
+
+ for (int i = 0, max = properties.size(); i < max; i++) {
+ XmlProperty property = properties.get(i);
+
+ AttributeDescriptor descriptor = property.getDescriptor();
+ if (descriptor.isDeprecated()) {
+ if (deprecatedProperties == null) {
+ deprecatedProperties = Lists.newArrayListWithExpectedSize(10);
+ }
+ deprecatedProperties.add(property);
+ continue;
+ }
+
+ String firstName = descriptor.getXmlLocalName();
+ if (firstName.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) {
+ if (marginProperties == null) {
+ marginProperties = Lists.newArrayListWithExpectedSize(5);
+ }
+ marginProperties.add(property);
+ } else {
+ layoutProperties.add(property);
+ }
+ continue;
+ }
+
+ if (firstName.equals(ATTR_ID)) {
+ // Add id to the front (though the layout parameters will be added to
+ // the front of this at the end)
+ property.setPriority(PRIO_FIRST);
+ collapsed.add(property);
+ continue;
+ }
+
+ String category = PropertyMetadata.getCategory(firstName);
+ if (category != null) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ if (complex == null) {
+ complex = new ComplexProperty(
+ category,
+ "[]",
+ null /* properties */);
+ categoryToProperty.put(category, complex);
+ complex.setPriority(property.getPriority());
+ }
+ categoryToProperties.put(category, property);
+ continue;
+ }
+
+ // Index of second word in the first name, so in fooBar it's 3 (index of 'B')
+ int firstNameIndex = firstName.length();
+ for (int k = 0, kn = firstName.length(); k < kn; k++) {
+ if (Character.isUpperCase(firstName.charAt(k))) {
+ firstNameIndex = k;
+ break;
+ }
+ }
+
+ // Scout forwards and see how many properties we can combine
+ int j = i + 1;
+ if (property.getCategory() != PropertyCategory.PREFERRED
+ && !property.getDescriptor().isDeprecated()) {
+ for (; j < max; j++) {
+ XmlProperty next = properties.get(j);
+ String nextName = next.getName();
+ if (nextName.regionMatches(0, firstName, 0, firstNameIndex)
+ // Also make sure we begin the second word at the next
+ // character; if not, we could have something like
+ // scrollBar
+ // scrollingBehavior
+ && nextName.length() > firstNameIndex
+ && Character.isUpperCase(nextName.charAt(firstNameIndex))) {
+
+ // Deprecated attributes, and preferred attributes, should not
+ // be pushed into normal clusters (preferred stay top-level
+ // and sort to the top, deprecated are all put in the same cluster at
+ // the end)
+
+ if (next.getCategory() == PropertyCategory.PREFERRED) {
+ break;
+ }
+ if (next.getDescriptor().isDeprecated()) {
+ break;
+ }
+
+ // This property should be combined with the previous
+ // property
+ } else {
+ break;
+ }
+ }
+ }
+ if (j - i > 1) {
+ // Combining multiple properties: all the properties from i
+ // through j inclusive
+ XmlProperty[] subprops = new XmlProperty[j - i];
+ for (int k = i, index = 0; k < j; k++, index++) {
+ subprops[index] = properties.get(k);
+ }
+ Arrays.sort(subprops, Property.PRIORITY);
+
+ // See if we can compute a LONGER base than just the first word.
+ // For example, if we have "lineSpacingExtra" and "lineSpacingMultiplier"
+ // we'd like the base to be "lineSpacing", not "line".
+ int common = firstNameIndex;
+ for (int k = firstNameIndex + 1, n = firstName.length(); k < n; k++) {
+ if (Character.isUpperCase(firstName.charAt(k))) {
+ common = k;
+ break;
+ }
+ }
+ if (common > firstNameIndex) {
+ for (int k = 0, n = subprops.length; k < n; k++) {
+ String nextName = subprops[k].getName();
+ if (nextName.regionMatches(0, firstName, 0, common)
+ // Also make sure we begin the second word at the next
+ // character; if not, we could have something like
+ // scrollBar
+ // scrollingBehavior
+ && nextName.length() > common
+ && Character.isUpperCase(nextName.charAt(common))) {
+ // New prefix is okay
+ } else {
+ common = firstNameIndex;
+ break;
+ }
+ }
+ firstNameIndex = common;
+ }
+
+ String base = firstName.substring(0, firstNameIndex);
+ base = DescriptorsUtils.capitalize(base);
+ Property complexProperty = new ComplexProperty(
+ base,
+ "[]",
+ subprops);
+ complexProperty.setPriority(subprops[0].getPriority());
+ //complexProperty.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(complexProperty);
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (XmlProperty p : subprops) {
+ p.setParent(complexProperty);
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complexProperty.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complexProperty.setCategory(PropertyCategory.PREFERRED);
+ }
+ } else {
+ // Add the individual properties (usually 1, sometimes 2
+ for (int k = i; k < j; k++) {
+ collapsed.add(properties.get(k));
+ }
+ }
+
+ i = j - 1; // -1: compensate in advance for the for-loop adding 1
+ }
+
+ // Update the complex properties
+ for (String category : categoryToProperties.keySet()) {
+ Collection<Property> subProperties = categoryToProperties.get(category);
+ if (subProperties.size() > 1) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ assert complex != null : category;
+ Property[] subArray = new Property[subProperties.size()];
+ complex.setProperties(subProperties.toArray(subArray));
+ complex.setPriority(subArray[0].getPriority());
+ collapsed.add(complex);
+
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (Property p : subProperties) {
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complex.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complex.setCategory(PropertyCategory.ADVANCED);
+ }
+ } else if (subProperties.size() == 1) {
+ collapsed.add(subProperties.iterator().next());
+ }
+ }
+
+ if (layoutProperties.size() > 0 || marginProperties != null) {
+ if (marginProperties != null) {
+ XmlProperty[] m =
+ marginProperties.toArray(new XmlProperty[marginProperties.size()]);
+ Property marginProperty = new ComplexProperty(
+ "Margins",
+ "[]",
+ m);
+ layoutProperties.add(marginProperty);
+ marginProperty.setPriority(PRIO_LAST);
+
+ for (XmlProperty p : m) {
+ p.setParent(marginProperty);
+ }
+ }
+ Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]);
+ Arrays.sort(l, Property.PRIORITY);
+ Property property = new ComplexProperty(
+ "Layout Parameters",
+ "[]",
+ l);
+ for (Property p : l) {
+ if (p instanceof XmlProperty) {
+ ((XmlProperty) p).setParent(property);
+ }
+ }
+ property.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(property);
+ property.setPriority(PRIO_SECOND);
+ }
+
+ if (deprecatedProperties != null && deprecatedProperties.size() > 0) {
+ Property property = new ComplexProperty(
+ "Deprecated",
+ "(Deprecated Properties)",
+ deprecatedProperties.toArray(new Property[deprecatedProperties.size()]));
+ property.setPriority(PRIO_LAST);
+ collapsed.add(property);
+ }
+
+ Collections.sort(collapsed, Property.PRIORITY);
+
+ return collapsed;
+ }
+
+ @Nullable
+ GraphicalEditorPart getGraphicalEditor() {
+ return mGraphicalEditorPart;
+ }
+
+ // HACK: This should be passed into each property instead
+ public Object getCurrentViewObject() {
+ return mCurrentViewCookie;
+ }
+
+ public void setSortingMode(SortingMode sortingMode) {
+ mSortMode = sortingMode;
+ }
+
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574
+ public static Composite addWorkaround(Composite parent) {
+ if (ButtonPropertyEditorPresentation.isInWorkaround) {
+ Composite top = new Composite(parent, SWT.NONE);
+ top.setLayout(new GridLayout(1, false));
+ Label label = new Label(top, SWT.WRAP);
+ label.setText(
+ "This dialog is shown instead of an inline text editor as a\n" +
+ "workaround for an Eclipse bug specific to OSX Mountain Lion.\n" +
+ "It should be fixed in Eclipse 4.3.");
+ label.setForeground(top.getDisplay().getSystemColor(SWT.COLOR_RED));
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = false;
+ data.grabExcessHorizontalSpace = false;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.BEGINNING;
+ label.setLayoutData(data);
+
+ Link link = new Link(top, SWT.NO_FOCUS);
+ link.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ link.setText("<a>https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574</a>");
+ link.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ try {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser();
+ browser.openURL(new URL(event.text));
+ } catch (Exception e) {
+ String message = String.format(
+ "Could not open browser. Vist\n%1$s\ninstead.",
+ event.text);
+ MessageDialog.openError(((Link)event.getSource()).getShell(),
+ "Browser Error", message);
+ }
+ }
+ });
+
+ return top;
+ }
+
+ return null;
+ }
+}