aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java121
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptorLabelProvider.java87
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java33
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java961
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java57
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java485
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/EnumAttributeDescriptor.java42
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/FlagAttributeDescriptor.java92
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IDescriptorProvider.java24
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ITextAttributeCreator.java47
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IUnknownDescriptorProvider.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ListAttributeDescriptor.java89
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ReferenceAttributeDescriptor.java108
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/SeparatorAttributeDescriptor.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextAttributeDescriptor.java290
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextValueDescriptor.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/XmlnsAttributeDescriptor.java77
17 files changed, 2646 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java
new file mode 100644
index 000000000..345a109e6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * {@link AttributeDescriptor} describes an XML attribute with its XML attribute name.
+ * <p/>
+ * An attribute descriptor also knows which UI node should be instantiated to represent
+ * this particular attribute (e.g. text field, icon chooser, class selector, etc.)
+ * Some attributes may be hidden and have no user interface at all.
+ * <p/>
+ * This is an abstract class. Derived classes must implement data description and return
+ * the correct UiAttributeNode-derived class.
+ */
+public abstract class AttributeDescriptor implements Comparable<AttributeDescriptor> {
+ public static final String ATTRIBUTE_ICON_FILENAME = "attribute"; //$NON-NLS-1$
+
+ private final String mXmlLocalName;
+ private final String mNsUri;
+ private final IAttributeInfo mAttrInfo;
+ private ElementDescriptor mParent;
+
+ /**
+ * Creates a new {@link AttributeDescriptor}
+ *
+ * @param xmlLocalName The XML name of the attribute (case sensitive)
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null for a "real"
+ * attribute representing a View element's attribute. Can be null for some
+ * specialized internal attribute descriptors (e.g. hidden descriptors, XMLNS,
+ * or attribute separator, all of which do not represent any real attribute.)
+ */
+ public AttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ assert xmlLocalName != null;
+ mXmlLocalName = xmlLocalName;
+ mNsUri = nsUri;
+ mAttrInfo = attrInfo;
+ }
+
+ /** Returns the XML local name of the attribute (case sensitive). */
+ public final String getXmlLocalName() {
+ return mXmlLocalName;
+ }
+
+ /** Returns the namespace URI of this attribute. */
+ public final String getNamespaceUri() {
+ return mNsUri;
+ }
+
+ /** Sets the element descriptor to which this attribute is attached. */
+ final void setParent(ElementDescriptor parent) {
+ mParent = parent;
+ }
+
+ /** Returns the element descriptor to which this attribute is attached. */
+ public final ElementDescriptor getParent() {
+ return mParent;
+ }
+
+ /** Returns whether this attribute is deprecated (based on its attrs.xml javadoc.) */
+ public boolean isDeprecated() {
+ return mAttrInfo == null ? false : mAttrInfo.getDeprecatedDoc() != null;
+ }
+
+ /**
+ * Returns the {@link IAttributeInfo} of this attribute.
+ * Can't be null for real attributes.
+ * Can be null for specialized internal attribute descriptors that do not correspond to
+ * any real XML attribute.
+ */
+ public IAttributeInfo getAttributeInfo() {
+ return mAttrInfo;
+ }
+
+ /**
+ * Returns an optional icon for the attribute.
+ * This icon is generic, that is all attribute descriptors have the same icon
+ * no matter what they represent.
+ *
+ * @return An icon for this element or null.
+ */
+ public Image getGenericIcon() {
+ return IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME);
+ }
+
+ /**
+ * @param uiParent The {@link UiElementNode} parent of this UI attribute.
+ * @return A new {@link UiAttributeNode} linked to this descriptor or null if this
+ * attribute has no user interface.
+ */
+ public abstract UiAttributeNode createUiNode(UiElementNode uiParent);
+
+ // Implements Comparable<AttributeDescriptor>
+ @Override
+ public int compareTo(AttributeDescriptor other) {
+ return mXmlLocalName.compareTo(other.mXmlLocalName);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptorLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptorLabelProvider.java
new file mode 100644
index 000000000..32def6456
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptorLabelProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAbstractTextAttributeNode;
+
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * Label provider for {@link UiAbstractTextAttributeNode}.
+ */
+public class AttributeDescriptorLabelProvider implements ILabelProvider {
+
+ private final static AttributeDescriptorLabelProvider sThis =
+ new AttributeDescriptorLabelProvider();
+
+ public static ILabelProvider getProvider() {
+ return sThis;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof UiAbstractTextAttributeNode) {
+ UiAbstractTextAttributeNode node = (UiAbstractTextAttributeNode) element;
+ if (node.getDescriptor().isDeprecated()) {
+ String v = node.getCurrentValue();
+ if (v != null && v.length() > 0) {
+ IconFactory factory = IconFactory.getInstance();
+ return factory.getIcon("warning"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof UiAbstractTextAttributeNode) {
+ return ((UiAbstractTextAttributeNode)element).getCurrentValue();
+ }
+
+ return null;
+ }
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void dispose() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java
new file mode 100644
index 000000000..7d76687c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiListAttributeNode;
+
+/**
+ * Describes a text attribute that can only contain boolean values.
+ * It is displayed by a {@link UiListAttributeNode}.
+ */
+public class BooleanAttributeDescriptor extends ListAttributeDescriptor {
+ private static final String[] VALUES = new String[] { "true", "false" }; //$NON-NLS-1$ //$NON-NLS-2$
+
+ public BooleanAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo, VALUES);
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
new file mode 100644
index 000000000..da3a1856c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
@@ -0,0 +1,961 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_TEXT;
+import static com.android.SdkConstants.EDIT_TEXT;
+import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
+import static com.android.SdkConstants.FQCN_ADAPTER_VIEW;
+import static com.android.SdkConstants.GALLERY;
+import static com.android.SdkConstants.GRID_LAYOUT;
+import static com.android.SdkConstants.GRID_VIEW;
+import static com.android.SdkConstants.GT_ENTITY;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.LIST_VIEW;
+import static com.android.SdkConstants.LT_ENTITY;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.RELATIVE_LAYOUT;
+import static com.android.SdkConstants.REQUEST_FOCUS;
+import static com.android.SdkConstants.SPACE;
+import static com.android.SdkConstants.VALUE_FILL_PARENT;
+import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.SdkConstants.VIEW_MERGE;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.resources.ResourceType;
+
+import org.eclipse.swt.graphics.Image;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Utility methods related to descriptors handling.
+ */
+public final class DescriptorsUtils {
+ private static final String DEFAULT_WIDGET_PREFIX = "widget";
+
+ private static final int JAVADOC_BREAK_LENGTH = 60;
+
+ /**
+ * The path in the online documentation for the manifest description.
+ * <p/>
+ * This is NOT a complete URL. To be used, it needs to be appended
+ * to {@link AdtConstants#CODESITE_BASE_URL} or to the local SDK
+ * documentation.
+ */
+ public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$
+
+ public static final String IMAGE_KEY = "image"; //$NON-NLS-1$
+
+ private static final String CODE = "$code"; //$NON-NLS-1$
+ private static final String LINK = "$link"; //$NON-NLS-1$
+ private static final String ELEM = "$elem"; //$NON-NLS-1$
+ private static final String BREAK = "$break"; //$NON-NLS-1$
+
+ /**
+ * Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
+ *
+ * @param attributes The list of {@link AttributeDescriptor} to append to
+ * @param elementXmlName Optional XML local name of the element to which attributes are
+ * being added. When not null, this is used to filter overrides.
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param infos The array of {@link AttributeInfo} to read and append to attributes
+ * @param requiredAttributes An optional set of attributes to mark as "required" (i.e. append
+ * a "*" to their UI name as a hint for the user.) If not null, must contains
+ * entries in the form "elem-name/attr-name". Elem-name can be "*".
+ * @param overrides A map [attribute name => ITextAttributeCreator creator].
+ */
+ public static void appendAttributes(List<AttributeDescriptor> attributes,
+ String elementXmlName,
+ String nsUri, AttributeInfo[] infos,
+ Set<String> requiredAttributes,
+ Map<String, ITextAttributeCreator> overrides) {
+ for (AttributeInfo info : infos) {
+ boolean required = false;
+ if (requiredAttributes != null) {
+ String attr_name = info.getName();
+ if (requiredAttributes.contains("*/" + attr_name) ||
+ requiredAttributes.contains(elementXmlName + "/" + attr_name)) {
+ required = true;
+ }
+ }
+ appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides);
+ }
+ }
+
+ /**
+ * Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
+ *
+ * @param attributes The list of {@link AttributeDescriptor} to append to
+ * @param elementXmlName Optional XML local name of the element to which attributes are
+ * being added. When not null, this is used to filter overrides.
+ * @param info The {@link AttributeInfo} to append to attributes
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param required True if the attribute is to be marked as "required" (i.e. append
+ * a "*" to its UI name as a hint for the user.)
+ * @param overrides A map [attribute name => ITextAttributeCreator creator].
+ */
+ public static void appendAttribute(List<AttributeDescriptor> attributes,
+ String elementXmlName,
+ String nsUri,
+ AttributeInfo info, boolean required,
+ Map<String, ITextAttributeCreator> overrides) {
+ TextAttributeDescriptor attr = null;
+
+ String xmlLocalName = info.getName();
+
+ // Add the known types to the tooltip
+ EnumSet<Format> formats_set = info.getFormats();
+ int flen = formats_set.size();
+ if (flen > 0) {
+ // Create a specialized attribute if we can
+ if (overrides != null) {
+ for (Entry<String, ITextAttributeCreator> entry: overrides.entrySet()) {
+ // The override key can have the following formats:
+ // */xmlLocalName
+ // element/xmlLocalName
+ // element1,element2,...,elementN/xmlLocalName
+ String key = entry.getKey();
+ String elements[] = key.split("/"); //$NON-NLS-1$
+ String overrideAttrLocalName = null;
+ if (elements.length < 1) {
+ continue;
+ } else if (elements.length == 1) {
+ overrideAttrLocalName = elements[0];
+ elements = null;
+ } else {
+ overrideAttrLocalName = elements[elements.length - 1];
+ elements = elements[0].split(","); //$NON-NLS-1$
+ }
+
+ if (overrideAttrLocalName == null ||
+ !overrideAttrLocalName.equals(xmlLocalName)) {
+ continue;
+ }
+
+ boolean ok_element = elements != null && elements.length < 1;
+ if (!ok_element && elements != null) {
+ for (String element : elements) {
+ if (element.equals("*") //$NON-NLS-1$
+ || element.equals(elementXmlName)) {
+ ok_element = true;
+ break;
+ }
+ }
+ }
+
+ if (!ok_element) {
+ continue;
+ }
+
+ ITextAttributeCreator override = entry.getValue();
+ if (override != null) {
+ attr = override.create(xmlLocalName, nsUri, info);
+ }
+ }
+ } // if overrides
+
+ // Create a specialized descriptor if we can, based on type
+ if (attr == null) {
+ if (formats_set.contains(Format.REFERENCE)) {
+ // This is either a multi-type reference or a generic reference.
+ attr = new ReferenceAttributeDescriptor(
+ xmlLocalName, nsUri, info);
+ } else if (formats_set.contains(Format.ENUM)) {
+ attr = new ListAttributeDescriptor(
+ xmlLocalName, nsUri, info);
+ } else if (formats_set.contains(Format.FLAG)) {
+ attr = new FlagAttributeDescriptor(
+ xmlLocalName, nsUri, info);
+ } else if (formats_set.contains(Format.BOOLEAN)) {
+ attr = new BooleanAttributeDescriptor(
+ xmlLocalName, nsUri, info);
+ } else if (formats_set.contains(Format.STRING)) {
+ attr = new ReferenceAttributeDescriptor(
+ ResourceType.STRING, xmlLocalName, nsUri, info);
+ }
+ }
+ }
+
+ // By default a simple text field is used
+ if (attr == null) {
+ attr = new TextAttributeDescriptor(xmlLocalName, nsUri, info);
+ }
+
+ if (required) {
+ attr.setRequired(true);
+ }
+
+ attributes.add(attr);
+ }
+
+ /**
+ * Indicates the the given {@link AttributeInfo} already exists in the ArrayList of
+ * {@link AttributeDescriptor}. This test for the presence of a descriptor with the same
+ * XML name.
+ *
+ * @param attributes The list of {@link AttributeDescriptor} to compare to.
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param info The {@link AttributeInfo} to know whether it is included in the above list.
+ * @return True if this {@link AttributeInfo} is already present in
+ * the {@link AttributeDescriptor} list.
+ */
+ public static boolean containsAttribute(ArrayList<AttributeDescriptor> attributes,
+ String nsUri,
+ AttributeInfo info) {
+ String xmlLocalName = info.getName();
+ for (AttributeDescriptor desc : attributes) {
+ if (desc.getXmlLocalName().equals(xmlLocalName)) {
+ if (nsUri == desc.getNamespaceUri() ||
+ (nsUri != null && nsUri.equals(desc.getNamespaceUri()))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create a pretty attribute UI name from an XML name.
+ * <p/>
+ * The original xml name starts with a lower case and is camel-case,
+ * e.g. "maxWidthForView". The pretty name starts with an upper case
+ * and has space separators, e.g. "Max width for view".
+ */
+ public static String prettyAttributeUiName(String name) {
+ if (name.length() < 1) {
+ return name;
+ }
+ StringBuilder buf = new StringBuilder(2 * name.length());
+
+ char c = name.charAt(0);
+ // Use upper case initial letter
+ buf.append(Character.toUpperCase(c));
+ int len = name.length();
+ for (int i = 1; i < len; i++) {
+ c = name.charAt(i);
+ if (Character.isUpperCase(c)) {
+ // Break camel case into separate words
+ buf.append(' ');
+ // Use a lower case initial letter for the next word, except if the
+ // word is solely X, Y or Z.
+ if (c >= 'X' && c <= 'Z' &&
+ (i == len-1 ||
+ (i < len-1 && Character.isUpperCase(name.charAt(i+1))))) {
+ buf.append(c);
+ } else {
+ buf.append(Character.toLowerCase(c));
+ }
+ } else if (c == '_') {
+ buf.append(' ');
+ } else {
+ buf.append(c);
+ }
+ }
+
+ name = buf.toString();
+
+ name = replaceAcronyms(name);
+
+ return name;
+ }
+
+ /**
+ * Similar to {@link #prettyAttributeUiName(String)}, but it will capitalize
+ * all words, not just the first one.
+ * <p/>
+ * The original xml name starts with a lower case and is camel-case, e.g.
+ * "maxWidthForView". The corresponding return value is
+ * "Max Width For View".
+ *
+ * @param name the attribute name, which should be a camel case name, e.g.
+ * "maxWidth"
+ * @return the corresponding display name, e.g. "Max Width"
+ */
+ @NonNull
+ public static String capitalize(@NonNull String name) {
+ if (name.isEmpty()) {
+ return name;
+ }
+ StringBuilder buf = new StringBuilder(2 * name.length());
+
+ char c = name.charAt(0);
+ // Use upper case initial letter
+ buf.append(Character.toUpperCase(c));
+ int len = name.length();
+ for (int i = 1; i < len; i++) {
+ c = name.charAt(i);
+ if (Character.isUpperCase(c)) {
+ // Break camel case into separate words
+ buf.append(' ');
+ // Use a lower case initial letter for the next word, except if the
+ // word is solely X, Y or Z.
+ buf.append(c);
+ } else if (c == '_') {
+ buf.append(' ');
+ if (i < len -1 && Character.isLowerCase(name.charAt(i + 1))) {
+ buf.append(Character.toUpperCase(name.charAt(i + 1)));
+ i++;
+ }
+ } else {
+ buf.append(c);
+ }
+ }
+
+ name = buf.toString();
+
+ name = replaceAcronyms(name);
+
+ return name;
+ }
+
+ private static String replaceAcronyms(String name) {
+ // Replace these acronyms by upper-case versions
+ // - (?<=^| ) means "if preceded by a space or beginning of string"
+ // - (?=$| ) means "if followed by a space or end of string"
+ if (name.contains("sdk") || name.contains("Sdk")) {
+ name = name.replaceAll("(?<=^| )[sS]dk(?=$| )", "SDK");
+ }
+ if (name.contains("uri") || name.contains("Uri")) {
+ name = name.replaceAll("(?<=^| )[uU]ri(?=$| )", "URI");
+ }
+ if (name.contains("ime") || name.contains("Ime")) {
+ name = name.replaceAll("(?<=^| )[iI]me(?=$| )", "IME");
+ }
+ if (name.contains("vm") || name.contains("Vm")) {
+ name = name.replaceAll("(?<=^| )[vV]m(?=$| )", "VM");
+ }
+ if (name.contains("ui") || name.contains("Ui")) {
+ name = name.replaceAll("(?<=^| )[uU]i(?=$| )", "UI");
+ }
+ return name;
+ }
+
+ /**
+ * Formats the javadoc tooltip to be usable in a tooltip.
+ */
+ public static String formatTooltip(String javadoc) {
+ ArrayList<String> spans = scanJavadoc(javadoc);
+
+ StringBuilder sb = new StringBuilder();
+ boolean needBreak = false;
+
+ for (int n = spans.size(), i = 0; i < n; ++i) {
+ String s = spans.get(i);
+ if (CODE.equals(s)) {
+ s = spans.get(++i);
+ if (s != null) {
+ sb.append('"').append(s).append('"');
+ }
+ } else if (LINK.equals(s)) {
+ String base = spans.get(++i);
+ String anchor = spans.get(++i);
+ String text = spans.get(++i);
+
+ if (base != null) {
+ base = base.trim();
+ }
+ if (anchor != null) {
+ anchor = anchor.trim();
+ }
+ if (text != null) {
+ text = text.trim();
+ }
+
+ // If there's no text, use the anchor if there's one
+ if (text == null || text.length() == 0) {
+ text = anchor;
+ }
+
+ if (base != null && base.length() > 0) {
+ if (text == null || text.length() == 0) {
+ // If we still have no text, use the base as text
+ text = base;
+ }
+ }
+
+ if (text != null) {
+ sb.append(text);
+ }
+
+ } else if (ELEM.equals(s)) {
+ s = spans.get(++i);
+ if (s != null) {
+ sb.append(s);
+ }
+ } else if (BREAK.equals(s)) {
+ needBreak = true;
+ } else if (s != null) {
+ if (needBreak && s.trim().length() > 0) {
+ sb.append('\n');
+ }
+ sb.append(s);
+ needBreak = false;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Formats the javadoc tooltip to be usable in a FormText.
+ * <p/>
+ * If the descriptor can provide an icon, the caller should provide
+ * elementsDescriptor.getIcon() as "image" to FormText, e.g.:
+ * <code>formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon());</code>
+ *
+ * @param javadoc The javadoc to format. Cannot be null.
+ * @param elementDescriptor The element descriptor parent of the javadoc. Cannot be null.
+ * @param androidDocBaseUrl The base URL for the documentation. Cannot be null. Should be
+ * <code>FrameworkResourceManager.getInstance().getDocumentationBaseUrl()</code>
+ */
+ public static String formatFormText(String javadoc,
+ ElementDescriptor elementDescriptor,
+ String androidDocBaseUrl) {
+ ArrayList<String> spans = scanJavadoc(javadoc);
+
+ String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL;
+ String sdkUrl = elementDescriptor.getSdkUrl();
+ if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) {
+ fullSdkUrl = androidDocBaseUrl + sdkUrl;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ Image icon = elementDescriptor.getCustomizedIcon();
+ if (icon != null) {
+ sb.append("<form><li style=\"image\" value=\"" + //$NON-NLS-1$
+ IMAGE_KEY + "\">"); //$NON-NLS-1$
+ } else {
+ sb.append("<form><p>"); //$NON-NLS-1$
+ }
+
+ for (int n = spans.size(), i = 0; i < n; ++i) {
+ String s = spans.get(i);
+ if (CODE.equals(s)) {
+ s = spans.get(++i);
+ if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) {
+ sb.append("<a href=\""); //$NON-NLS-1$
+ sb.append(fullSdkUrl);
+ sb.append("\">"); //$NON-NLS-1$
+ sb.append(s);
+ sb.append("</a>"); //$NON-NLS-1$
+ } else if (s != null) {
+ sb.append('"').append(s).append('"');
+ }
+ } else if (LINK.equals(s)) {
+ String base = spans.get(++i);
+ String anchor = spans.get(++i);
+ String text = spans.get(++i);
+
+ if (base != null) {
+ base = base.trim();
+ }
+ if (anchor != null) {
+ anchor = anchor.trim();
+ }
+ if (text != null) {
+ text = text.trim();
+ }
+
+ // If there's no text, use the anchor if there's one
+ if (text == null || text.length() == 0) {
+ text = anchor;
+ }
+
+ // TODO specialize with a base URL for views, menus & other resources
+ // Base is empty for a local page anchor, in which case we'll replace it
+ // by the element SDK URL if it exists.
+ if ((base == null || base.length() == 0) && fullSdkUrl != null) {
+ base = fullSdkUrl;
+ }
+
+ String url = null;
+ if (base != null && base.length() > 0) {
+ if (base.startsWith("http")) { //$NON-NLS-1$
+ // If base looks an URL, use it, with the optional anchor
+ url = base;
+ if (anchor != null && anchor.length() > 0) {
+ // If the base URL already has an anchor, it needs to be
+ // removed first. If there's no anchor, we need to add "#"
+ int pos = url.lastIndexOf('#');
+ if (pos < 0) {
+ url += "#"; //$NON-NLS-1$
+ } else if (pos < url.length() - 1) {
+ url = url.substring(0, pos + 1);
+ }
+
+ url += anchor;
+ }
+ } else if (text == null || text.length() == 0) {
+ // If we still have no text, use the base as text
+ text = base;
+ }
+ }
+
+ if (url != null && text != null) {
+ sb.append("<a href=\""); //$NON-NLS-1$
+ sb.append(url);
+ sb.append("\">"); //$NON-NLS-1$
+ sb.append(text);
+ sb.append("</a>"); //$NON-NLS-1$
+ } else if (text != null) {
+ sb.append("<b>").append(text).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ } else if (ELEM.equals(s)) {
+ s = spans.get(++i);
+ if (sdkUrl != null && s != null) {
+ sb.append("<a href=\""); //$NON-NLS-1$
+ sb.append(sdkUrl);
+ sb.append("\">"); //$NON-NLS-1$
+ sb.append(s);
+ sb.append("</a>"); //$NON-NLS-1$
+ } else if (s != null) {
+ sb.append("<b>").append(s).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ } else if (BREAK.equals(s)) {
+ // ignore line breaks in pseudo-HTML rendering
+ } else if (s != null) {
+ sb.append(s);
+ }
+ }
+
+ if (icon != null) {
+ sb.append("</li></form>"); //$NON-NLS-1$
+ } else {
+ sb.append("</p></form>"); //$NON-NLS-1$
+ }
+ return sb.toString();
+ }
+
+ private static ArrayList<String> scanJavadoc(String javadoc) {
+ ArrayList<String> spans = new ArrayList<String>();
+
+ // Standardize all whitespace in the javadoc to single spaces.
+ if (javadoc != null) {
+ javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Detects {@link <base>#<name> <text>} where all 3 are optional
+ Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$
+ // Detects <code>blah</code>
+ Pattern p_code = Pattern.compile("<code>(.+?)</code>(.*)"); //$NON-NLS-1$
+ // Detects @blah@, used in hard-coded tooltip descriptors
+ Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$
+ // Detects a buffer that starts by @@ (request for a break)
+ Pattern p_break = Pattern.compile("@@(.*)"); //$NON-NLS-1$
+ // Detects a buffer that starts by @ < or { (one that was not matched above)
+ Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$
+ // Detects everything till the next potential separator, i.e. @ < or {
+ Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$
+
+ int currentLength = 0;
+ String text = null;
+
+ while(javadoc != null && javadoc.length() > 0) {
+ Matcher m;
+ String s = null;
+ if ((m = p_code.matcher(javadoc)).matches()) {
+ spans.add(CODE);
+ spans.add(text = cleanupJavadocHtml(m.group(1))); // <code> text
+ javadoc = m.group(2);
+ if (text != null) {
+ currentLength += text.length();
+ }
+ } else if ((m = p_link.matcher(javadoc)).matches()) {
+ spans.add(LINK);
+ spans.add(m.group(1)); // @link base
+ spans.add(m.group(2)); // @link anchor
+ spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text
+ javadoc = m.group(4);
+ if (text != null) {
+ currentLength += text.length();
+ }
+ } else if ((m = p_elem.matcher(javadoc)).matches()) {
+ spans.add(ELEM);
+ spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@
+ javadoc = m.group(2);
+ if (text != null) {
+ currentLength += text.length() - 2;
+ }
+ } else if ((m = p_break.matcher(javadoc)).matches()) {
+ spans.add(BREAK);
+ currentLength = 0;
+ javadoc = m.group(1);
+ } else if ((m = p_open.matcher(javadoc)).matches()) {
+ s = m.group(1);
+ javadoc = m.group(2);
+ } else if ((m = p_text.matcher(javadoc)).matches()) {
+ s = m.group(1);
+ javadoc = m.group(2);
+ } else {
+ // This is not supposed to happen. In case of, just use everything.
+ s = javadoc;
+ javadoc = null;
+ }
+ if (s != null && s.length() > 0) {
+ s = cleanupJavadocHtml(s);
+
+ if (currentLength >= JAVADOC_BREAK_LENGTH) {
+ spans.add(BREAK);
+ currentLength = 0;
+ }
+ while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) {
+ int pos = s.indexOf(' ', JAVADOC_BREAK_LENGTH - currentLength);
+ if (pos <= 0) {
+ break;
+ }
+ spans.add(s.substring(0, pos + 1));
+ spans.add(BREAK);
+ currentLength = 0;
+ s = s.substring(pos + 1);
+ }
+
+ spans.add(s);
+ currentLength += s.length();
+ }
+ }
+
+ return spans;
+ }
+
+ /**
+ * Remove anything that looks like HTML from a javadoc snippet, as it is supported
+ * neither by FormText nor a standard text tooltip.
+ */
+ private static String cleanupJavadocHtml(String s) {
+ if (s != null) {
+ s = s.replaceAll(LT_ENTITY, "\""); //$NON-NLS-1$ $NON-NLS-2$
+ s = s.replaceAll(GT_ENTITY, "\""); //$NON-NLS-1$ $NON-NLS-2$
+ s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$
+ }
+ return s;
+ }
+
+ /**
+ * Returns the basename for the given fully qualified class name. It is okay to pass
+ * a basename to this method which will just be returned back.
+ *
+ * @param fqcn The fully qualified class name to convert
+ * @return the basename of the class name
+ */
+ public static String getBasename(String fqcn) {
+ String name = fqcn;
+ int lastDot = name.lastIndexOf('.');
+ if (lastDot != -1) {
+ name = name.substring(lastDot + 1);
+ }
+
+ return name;
+ }
+
+ /**
+ * Sets the default layout attributes for the a new UiElementNode.
+ * <p/>
+ * Note that ideally the node should already be part of a hierarchy so that its
+ * parent layout and previous sibling can be determined, if any.
+ * <p/>
+ * This does not override attributes which are not empty.
+ */
+ public static void setDefaultLayoutAttributes(UiElementNode node, boolean updateLayout) {
+ // if this ui_node is a layout and we're adding it to a document, use match_parent for
+ // both W/H. Otherwise default to wrap_layout.
+ ElementDescriptor descriptor = node.getDescriptor();
+
+ String name = descriptor.getXmlLocalName();
+ if (name.equals(REQUEST_FOCUS)) {
+ // Don't add ids, widths and heights etc to <requestFocus>
+ return;
+ }
+
+ // Width and height are mandatory in all layouts except GridLayout
+ boolean setSize = !node.getUiParent().getDescriptor().getXmlName().equals(GRID_LAYOUT);
+ if (setSize) {
+ boolean fill = descriptor.hasChildren() &&
+ node.getUiParent() instanceof UiDocumentNode;
+ node.setAttributeValue(
+ ATTR_LAYOUT_WIDTH,
+ ANDROID_URI,
+ fill ? VALUE_FILL_PARENT : VALUE_WRAP_CONTENT,
+ false /* override */);
+ node.setAttributeValue(
+ ATTR_LAYOUT_HEIGHT,
+ ANDROID_URI,
+ fill ? VALUE_FILL_PARENT : VALUE_WRAP_CONTENT,
+ false /* override */);
+ }
+
+ if (needsDefaultId(node.getDescriptor())) {
+ String freeId = getFreeWidgetId(node);
+ if (freeId != null) {
+ node.setAttributeValue(
+ ATTR_ID,
+ ANDROID_URI,
+ freeId,
+ false /* override */);
+ }
+ }
+
+ // Set a text attribute on textual widgets -- but only on those that define a text
+ // attribute
+ if (descriptor.definesAttribute(ANDROID_URI, ATTR_TEXT)
+ // Don't set default text value into edit texts - they typically start out blank
+ && !descriptor.getXmlLocalName().equals(EDIT_TEXT)) {
+ String type = getBasename(descriptor.getUiName());
+ node.setAttributeValue(
+ ATTR_TEXT,
+ ANDROID_URI,
+ type,
+ false /*override*/);
+ }
+
+ if (updateLayout) {
+ UiElementNode parent = node.getUiParent();
+ if (parent != null &&
+ parent.getDescriptor().getXmlLocalName().equals(
+ RELATIVE_LAYOUT)) {
+ UiElementNode previous = node.getUiPreviousSibling();
+ if (previous != null) {
+ String id = previous.getAttributeValue(ATTR_ID);
+ if (id != null && id.length() > 0) {
+ id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$
+ node.setAttributeValue(
+ ATTR_LAYOUT_BELOW,
+ ANDROID_URI,
+ id,
+ false /* override */);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines whether new views of the given type should be assigned a
+ * default id.
+ *
+ * @param descriptor a descriptor describing the view to look up
+ * @return true if new views of the given type should be assigned a default
+ * id
+ */
+ public static boolean needsDefaultId(ElementDescriptor descriptor) {
+ // By default, layouts do not need ids.
+ String tag = descriptor.getXmlLocalName();
+ if (tag.endsWith("Layout") //$NON-NLS-1$
+ || tag.equals(VIEW_INCLUDE)
+ || tag.equals(VIEW_MERGE)
+ || tag.equals(SPACE)
+ || tag.endsWith(SPACE) && tag.length() > SPACE.length() &&
+ tag.charAt(tag.length() - SPACE.length()) == '.') {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Given a UI node, returns the first available id that matches the
+ * pattern "prefix%d".
+ * <p/>TabWidget is a special case and the method will always return "@android:id/tabs".
+ *
+ * @param uiNode The UI node that gives the prefix to match.
+ * @return A suitable generated id in the attribute form needed by the XML id tag
+ * (e.g. "@+id/something")
+ */
+ public static String getFreeWidgetId(UiElementNode uiNode) {
+ String name = getBasename(uiNode.getDescriptor().getXmlLocalName());
+ return getFreeWidgetId(uiNode.getUiRoot(), name);
+ }
+
+ /**
+ * Given a UI root node and a potential XML node name, returns the first available
+ * id that matches the pattern "prefix%d".
+ * <p/>TabWidget is a special case and the method will always return "@android:id/tabs".
+ *
+ * @param uiRoot The root UI node to search for name conflicts from
+ * @param name The XML node prefix name to look for
+ * @return A suitable generated id in the attribute form needed by the XML id tag
+ * (e.g. "@+id/something")
+ */
+ public static String getFreeWidgetId(UiElementNode uiRoot, String name) {
+ if ("TabWidget".equals(name)) { //$NON-NLS-1$
+ return "@android:id/tabs"; //$NON-NLS-1$
+ }
+
+ return NEW_ID_PREFIX + getFreeWidgetId(uiRoot,
+ new Object[] { name, null, null, null });
+ }
+
+ /**
+ * Given a UI root node, returns the first available id that matches the
+ * pattern "prefix%d".
+ *
+ * For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters
+ * in methods and we're not going to do a dedicated type, we just use an object array which
+ * must contain one initial item and several are built on the fly just for internal storage:
+ * <ul>
+ * <li> prefix(String): The prefix of the generated id, i.e. "widget". Cannot be null.
+ * <li> index(Integer): The minimum index of the generated id. Must start with null.
+ * <li> generated(String): The generated widget currently being searched. Must start with null.
+ * <li> map(Set<String>): A set of the ids collected so far when walking through the widget
+ * hierarchy. Must start with null.
+ * </ul>
+ *
+ * @param uiRoot The Ui root node where to start searching recursively. For the initial call
+ * you want to pass the document root.
+ * @param params An in-out context of parameters used during recursion, as explained above.
+ * @return A suitable generated id
+ */
+ @SuppressWarnings("unchecked")
+ private static String getFreeWidgetId(UiElementNode uiRoot,
+ Object[] params) {
+
+ Set<String> map = (Set<String>)params[3];
+ if (map == null) {
+ params[3] = map = new HashSet<String>();
+ }
+
+ int num = params[1] == null ? 0 : ((Integer)params[1]).intValue();
+
+ String generated = (String) params[2];
+ String prefix = (String) params[0];
+ if (generated == null) {
+ int pos = prefix.indexOf('.');
+ if (pos >= 0) {
+ prefix = prefix.substring(pos + 1);
+ }
+ pos = prefix.indexOf('$');
+ if (pos >= 0) {
+ prefix = prefix.substring(pos + 1);
+ }
+ prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$
+ if (prefix.length() == 0) {
+ prefix = DEFAULT_WIDGET_PREFIX;
+ } else {
+ // Lowercase initial character
+ prefix = Character.toLowerCase(prefix.charAt(0)) + prefix.substring(1);
+ }
+
+ // Note that we perform locale-independent lowercase checks; in "Image" we
+ // want the lowercase version to be "image", not "?mage" where ? is
+ // the char LATIN SMALL LETTER DOTLESS I.
+ do {
+ num++;
+ generated = String.format("%1$s%2$d", prefix, num); //$NON-NLS-1$
+ } while (map.contains(generated.toLowerCase(Locale.US)));
+
+ params[0] = prefix;
+ params[1] = num;
+ params[2] = generated;
+ }
+
+ String id = uiRoot.getAttributeValue(ATTR_ID);
+ if (id != null) {
+ id = id.replace(NEW_ID_PREFIX, ""); //$NON-NLS-1$
+ id = id.replace(ID_PREFIX, ""); //$NON-NLS-1$
+ if (map.add(id.toLowerCase(Locale.US))
+ && map.contains(generated.toLowerCase(Locale.US))) {
+
+ do {
+ num++;
+ generated = String.format("%1$s%2$d", prefix, num); //$NON-NLS-1$
+ } while (map.contains(generated.toLowerCase(Locale.US)));
+
+ params[1] = num;
+ params[2] = generated;
+ }
+ }
+
+ for (UiElementNode uiChild : uiRoot.getUiChildren()) {
+ getFreeWidgetId(uiChild, params);
+ }
+
+ // Note: return params[2] (not "generated") since it could have changed during recursion.
+ return (String) params[2];
+ }
+
+ /**
+ * Returns true if the given descriptor represents a view that not only can have
+ * children but which allows us to <b>insert</b> children. Some views, such as
+ * ListView (and in general all AdapterViews), disallow children to be inserted except
+ * through the dedicated AdapterView interface to do it.
+ *
+ * @param descriptor the descriptor for the view in question
+ * @param viewObject an actual instance of the view, or null if not available
+ * @return true if the descriptor describes a view which allows insertion of child
+ * views
+ */
+ public static boolean canInsertChildren(ElementDescriptor descriptor, Object viewObject) {
+ if (descriptor.hasChildren()) {
+ if (viewObject != null) {
+ // We have a view object; see if it derives from an AdapterView
+ Class<?> clz = viewObject.getClass();
+ while (clz != null) {
+ if (clz.getName().equals(FQCN_ADAPTER_VIEW)) {
+ return false;
+ }
+ clz = clz.getSuperclass();
+ }
+ } else {
+ // No view object, so we can't easily look up the class and determine
+ // whether it's an AdapterView; instead, look at the fixed list of builtin
+ // concrete subclasses of AdapterView
+ String viewName = descriptor.getXmlLocalName();
+ if (viewName.equals(LIST_VIEW) || viewName.equals(EXPANDABLE_LIST_VIEW)
+ || viewName.equals(GALLERY) || viewName.equals(GRID_VIEW)) {
+
+ // We should really also enforce that
+ // XmlUtils.ANDROID_URI.equals(descriptor.getNameSpace())
+ // here and if not, return true, but it turns out the getNameSpace()
+ // for elements are often "".
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java
new file mode 100644
index 000000000..695327847
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DocumentDescriptor.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ * {@link DocumentDescriptor} describes the properties expected for an XML document node.
+ *
+ * Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name,
+ * tooltip, SDK url and attributes list.
+ * <p/>
+ * It has a children list which represent all the possible roots of the document.
+ * <p/>
+ * The document nodes are "mandatory", meaning the UI node is never deleted and it may lack
+ * an actual XML node attached.
+ */
+public class DocumentDescriptor extends ElementDescriptor {
+
+ /**
+ * Constructs a new {@link DocumentDescriptor} based on its XML name and children list.
+ * The UI name is build by capitalizing the XML name.
+ * The UI nodes will be non-mandatory.
+ * <p/>
+ * The XML name is never shown in the UI directly. It is however used when an icon
+ * needs to be found for the node.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param children The list of allowed children. Can be null or empty.
+ */
+ public DocumentDescriptor(String xml_name, ElementDescriptor[] children) {
+ super(xml_name, children, Mandatory.MANDATORY);
+ }
+
+ /**
+ * @return A new {@link UiElementNode} linked to this descriptor.
+ */
+ @Override
+ public UiElementNode createUiNode() {
+ return new UiDocumentNode(this);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
new file mode 100644
index 000000000..0d62ec00c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * {@link ElementDescriptor} describes the properties expected for a given XML element node.
+ *
+ * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url,
+ * an attributes list and a children list.
+ *
+ * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack
+ * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached
+ * and it will cease to exist when the XML node ceases to exist.
+ */
+public class ElementDescriptor implements Comparable<ElementDescriptor> {
+ private static final String ELEMENT_ICON_FILENAME = "element"; //$NON-NLS-1$
+
+ /** The XML element node name. Case sensitive. */
+ protected final String mXmlName;
+ /** The XML element name for the user interface, typically capitalized. */
+ private final String mUiName;
+ /** The list of allowed attributes. */
+ private AttributeDescriptor[] mAttributes;
+ /** The list of allowed children */
+ private ElementDescriptor[] mChildren;
+ /* An optional tooltip. Can be empty. */
+ private String mTooltip;
+ /** An optional SKD URL. Can be empty. */
+ private String mSdkUrl;
+ /** Whether this UI node must always exist (even for empty models). */
+ private final Mandatory mMandatory;
+
+ public enum Mandatory {
+ NOT_MANDATORY,
+ MANDATORY,
+ MANDATORY_LAST
+ }
+
+ /**
+ * Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
+ * tooltip, SDK url, attributes list, children list and mandatory.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param ui_name The XML element name for the user interface, typically capitalized.
+ * @param tooltip An optional tooltip. Can be null or empty.
+ * @param sdk_url An optional SKD URL. Can be null or empty.
+ * @param attributes The list of allowed attributes. Can be null or empty.
+ * @param children The list of allowed children. Can be null or empty.
+ * @param mandatory Whether this node must always exist (even for empty models). A mandatory
+ * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
+ * UI node MUST have an XML node attached and it will cease to exist when the XML node
+ * ceases to exist.
+ */
+ public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
+ AttributeDescriptor[] attributes,
+ ElementDescriptor[] children,
+ Mandatory mandatory) {
+ mMandatory = mandatory;
+ mXmlName = xml_name;
+ mUiName = ui_name;
+ mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
+ mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
+ setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
+ mChildren = children != null ? children : new ElementDescriptor[]{};
+ }
+
+ /**
+ * Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
+ * tooltip, SDK url, attributes list, children list and mandatory.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param ui_name The XML element name for the user interface, typically capitalized.
+ * @param tooltip An optional tooltip. Can be null or empty.
+ * @param sdk_url An optional SKD URL. Can be null or empty.
+ * @param attributes The list of allowed attributes. Can be null or empty.
+ * @param children The list of allowed children. Can be null or empty.
+ * @param mandatory Whether this node must always exist (even for empty models). A mandatory
+ * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
+ * UI node MUST have an XML node attached and it will cease to exist when the XML node
+ * ceases to exist.
+ */
+ public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
+ AttributeDescriptor[] attributes,
+ ElementDescriptor[] children,
+ boolean mandatory) {
+ mMandatory = mandatory ? Mandatory.MANDATORY : Mandatory.NOT_MANDATORY;
+ mXmlName = xml_name;
+ mUiName = ui_name;
+ mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
+ mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
+ setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
+ mChildren = children != null ? children : new ElementDescriptor[]{};
+ }
+
+ /**
+ * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
+ * The UI name is build by capitalizing the XML name.
+ * The UI nodes will be non-mandatory.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param children The list of allowed children. Can be null or empty.
+ * @param mandatory Whether this node must always exist (even for empty models). A mandatory
+ * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
+ * UI node MUST have an XML node attached and it will cease to exist when the XML node
+ * ceases to exist.
+ */
+ public ElementDescriptor(String xml_name, ElementDescriptor[] children, Mandatory mandatory) {
+ this(xml_name, prettyName(xml_name), null, null, null, children, mandatory);
+ }
+
+ /**
+ * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
+ * The UI name is build by capitalizing the XML name.
+ * The UI nodes will be non-mandatory.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param children The list of allowed children. Can be null or empty.
+ */
+ public ElementDescriptor(String xml_name, ElementDescriptor[] children) {
+ this(xml_name, prettyName(xml_name), null, null, null, children, false);
+ }
+
+ /**
+ * Constructs a new {@link ElementDescriptor} based on its XML name.
+ * The UI name is build by capitalizing the XML name.
+ * The UI nodes will be non-mandatory.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ */
+ public ElementDescriptor(String xml_name) {
+ this(xml_name, prettyName(xml_name), null, null, null, null, false);
+ }
+
+ /** Returns whether this node must always exist (even for empty models) */
+ public Mandatory getMandatory() {
+ return mMandatory;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s [%s, attr %d, children %d%s]", //$NON-NLS-1$
+ this.getClass().getSimpleName(),
+ mXmlName,
+ mAttributes != null ? mAttributes.length : 0,
+ mChildren != null ? mChildren.length : 0,
+ mMandatory != Mandatory.NOT_MANDATORY ? ", " + mMandatory.toString() : "" //$NON-NLS-1$ //$NON-NLS-2$
+ );
+ }
+
+ /**
+ * Returns the XML element node local name (case sensitive)
+ */
+ public final String getXmlLocalName() {
+ int pos = mXmlName.indexOf(':');
+ if (pos != -1) {
+ return mXmlName.substring(pos+1);
+ }
+ return mXmlName;
+ }
+
+ /**
+ * Returns the XML element node name, including the prefix.
+ * Case sensitive.
+ * <p/>
+ * In Android resources, the element node name for Android resources typically does not
+ * have a prefix and is typically the simple Java class name (e.g. "View"), whereas for
+ * custom views it is generally the fully qualified class name of the view (e.g.
+ * "com.mycompany.myapp.MyView").
+ * <p/>
+ * Most of the time you'll probably want to use {@link #getXmlLocalName()} to get a local
+ * name guaranteed without a prefix.
+ * <p/>
+ * Note that the prefix that <em>may</em> be available in this descriptor has nothing to
+ * do with the actual prefix the node might have (or needs to have) in the actual XML file
+ * since descriptors are fixed and do not depend on any current namespace defined in the
+ * target XML.
+ */
+ public String getXmlName() {
+ return mXmlName;
+ }
+
+ /**
+ * Returns the namespace of the attribute.
+ */
+ public final String getNamespace() {
+ // For now we hard-code the prefix as being "android"
+ if (mXmlName.startsWith(ANDROID_NS_NAME_PREFIX)) {
+ return ANDROID_URI;
+ }
+
+ return ""; //$NON-NLs-1$
+ }
+
+
+ /** Returns the XML element name for the user interface, typically capitalized. */
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /**
+ * Returns an icon for the element.
+ * This icon is generic, that is all element descriptors have the same icon
+ * no matter what they represent.
+ *
+ * @return An icon for this element or null.
+ * @see #getCustomizedIcon()
+ */
+ public Image getGenericIcon() {
+ return IconFactory.getInstance().getIcon(ELEMENT_ICON_FILENAME);
+ }
+
+ /**
+ * Returns an optional icon for the element, typically to be used in XML form trees.
+ * <p/>
+ * This icon is customized to the given descriptor, that is different elements
+ * will have different icons.
+ * <p/>
+ * By default this tries to return an icon based on the XML name of the element.
+ * If this fails, it tries to return the default Android logo as defined in the
+ * plugin. If all fails, it returns null.
+ *
+ * @return An icon for this element. This is never null.
+ */
+ public Image getCustomizedIcon() {
+ IconFactory factory = IconFactory.getInstance();
+ int color = hasChildren() ? IconFactory.COLOR_BLUE
+ : IconFactory.COLOR_GREEN;
+ int shape = hasChildren() ? IconFactory.SHAPE_RECT
+ : IconFactory.SHAPE_CIRCLE;
+ String name = mXmlName;
+
+ int pos = name.lastIndexOf('.');
+ if (pos != -1) {
+ // If the user uses a fully qualified name, such as
+ // "android.gesture.GestureOverlayView" in their XML, we need to
+ // look up only by basename
+ name = name.substring(pos + 1);
+ }
+ Image icon = factory.getIcon(name, color, shape);
+ if (icon == null) {
+ icon = getGenericIcon();
+ }
+ if (icon == null) {
+ icon = AdtPlugin.getAndroidLogo();
+ }
+ return icon;
+ }
+
+ /**
+ * Returns an optional ImageDescriptor for the element.
+ * <p/>
+ * By default this tries to return an image based on the XML name of the element.
+ * If this fails, it tries to return the default Android logo as defined in the
+ * plugin. If all fails, it returns null.
+ *
+ * @return An ImageDescriptor for this element or null.
+ */
+ public ImageDescriptor getImageDescriptor() {
+ IconFactory factory = IconFactory.getInstance();
+ int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
+ int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
+ ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape);
+ return id != null ? id : AdtPlugin.getAndroidLogoDesc();
+ }
+
+ /* Returns the list of allowed attributes. */
+ public AttributeDescriptor[] getAttributes() {
+ return mAttributes;
+ }
+
+ /** Sets the list of allowed attributes. */
+ public void setAttributes(AttributeDescriptor[] attributes) {
+ mAttributes = attributes;
+ for (AttributeDescriptor attribute : attributes) {
+ attribute.setParent(this);
+ }
+ }
+
+ /** Returns the list of allowed children */
+ public ElementDescriptor[] getChildren() {
+ return mChildren;
+ }
+
+ /** @return True if this descriptor has children available */
+ public boolean hasChildren() {
+ return mChildren.length > 0;
+ }
+
+ /**
+ * Checks whether this descriptor can accept the given descriptor type
+ * as a direct child.
+ *
+ * @return True if this descriptor can accept children of the given descriptor type.
+ * False if not accepted, no children allowed, or target is null.
+ */
+ public boolean acceptChild(ElementDescriptor target) {
+ if (target != null && mChildren.length > 0) {
+ String targetXmlName = target.getXmlName();
+ for (ElementDescriptor child : mChildren) {
+ if (child.getXmlName().equals(targetXmlName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /** Sets the list of allowed children. */
+ public void setChildren(ElementDescriptor[] newChildren) {
+ mChildren = newChildren;
+ }
+
+ /**
+ * Sets the list of allowed children.
+ * <p/>
+ * This is just a convenience method that converts a Collection into an array and
+ * calls {@link #setChildren(ElementDescriptor[])}.
+ * <p/>
+ * This means a <em>copy</em> of the collection is made. The collection is not
+ * stored by the recipient and can thus be altered by the caller.
+ */
+ public void setChildren(Collection<ElementDescriptor> newChildren) {
+ setChildren(newChildren.toArray(new ElementDescriptor[newChildren.size()]));
+ }
+
+ /**
+ * Returns an optional tooltip. Will be null if not present.
+ * <p/>
+ * The tooltip is based on the Javadoc of the element and already processed via
+ * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as
+ * a UI tooltip.
+ */
+ public String getTooltip() {
+ return mTooltip;
+ }
+
+ /** Returns an optional SKD URL. Will be null if not present. */
+ public String getSdkUrl() {
+ return mSdkUrl;
+ }
+
+ /** Sets the optional tooltip. Can be null or empty. */
+ public void setTooltip(String tooltip) {
+ mTooltip = tooltip;
+ }
+
+ /** Sets the optional SDK URL. Can be null or empty. */
+ public void setSdkUrl(String sdkUrl) {
+ mSdkUrl = sdkUrl;
+ }
+
+ /**
+ * @return A new {@link UiElementNode} linked to this descriptor.
+ */
+ public UiElementNode createUiNode() {
+ return new UiElementNode(this);
+ }
+
+ /**
+ * Returns the first children of this descriptor that describes the given XML element name.
+ * <p/>
+ * In recursive mode, searches the direct children first before descending in the hierarchy.
+ *
+ * @return The ElementDescriptor matching the requested XML node element name or null.
+ */
+ public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
+ return findChildrenDescriptorInternal(element_name, recursive, null);
+ }
+
+ private ElementDescriptor findChildrenDescriptorInternal(String element_name,
+ boolean recursive,
+ Set<ElementDescriptor> visited) {
+ if (recursive && visited == null) {
+ visited = new HashSet<ElementDescriptor>();
+ }
+
+ for (ElementDescriptor e : getChildren()) {
+ if (e.getXmlName().equals(element_name)) {
+ return e;
+ }
+ }
+
+ if (visited != null) {
+ visited.add(this);
+ }
+
+ if (recursive) {
+ for (ElementDescriptor e : getChildren()) {
+ if (visited != null) {
+ if (!visited.add(e)) { // Set.add() returns false if element is already present
+ continue;
+ }
+ }
+ ElementDescriptor f = e.findChildrenDescriptorInternal(element_name,
+ recursive, visited);
+ if (f != null) {
+ return f;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Utility helper than pretty-formats an XML Name for the UI.
+ * This is used by the simplified constructor that takes only an XML element name.
+ *
+ * @param xml_name The XML name to convert.
+ * @return The XML name with dashes replaced by spaces and capitalized.
+ */
+ private static String prettyName(String xml_name) {
+ char c[] = xml_name.toCharArray();
+ if (c.length > 0) {
+ c[0] = Character.toUpperCase(c[0]);
+ }
+ return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns true if this node defines the given attribute
+ *
+ * @param namespaceUri the namespace URI of the target attribute
+ * @param attributeName the attribute name
+ * @return true if this element defines an attribute of the given name and namespace
+ */
+ public boolean definesAttribute(String namespaceUri, String attributeName) {
+ for (AttributeDescriptor desc : mAttributes) {
+ if (desc.getXmlLocalName().equals(attributeName) &&
+ desc.getNamespaceUri().equals(namespaceUri)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Implements Comparable<ElementDescriptor>:
+ @Override
+ public int compareTo(ElementDescriptor o) {
+ return mUiName.compareToIgnoreCase(o.mUiName);
+ }
+
+ /**
+ * Ensures that this view descriptor's attribute list is up to date. This is
+ * always the case for all the builtin descriptors, but for example for a
+ * custom view, it could be changing dynamically so caches may have to be
+ * recomputed. This method will return true if nothing changed, and false if
+ * it recomputed its info.
+ *
+ * @return true if the attributes are already up to date and nothing changed
+ */
+ public boolean syncAttributes() {
+ return true;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/EnumAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/EnumAttributeDescriptor.java
new file mode 100644
index 000000000..29233571b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/EnumAttributeDescriptor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiListAttributeNode;
+
+/**
+ * Describes a text attribute that can only contains some predefined values.
+ * It is displayed by a {@link UiListAttributeNode}.
+ */
+public class EnumAttributeDescriptor extends ListAttributeDescriptor {
+
+ public EnumAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
+ String tooltip, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * @return A new {@link UiListAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiListAttributeNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/FlagAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/FlagAttributeDescriptor.java
new file mode 100644
index 000000000..4f4b21569
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/FlagAttributeDescriptor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.ui.FlagValueCellEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiFlagAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiListAttributeNode;
+
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Describes a text attribute that can only contains some predefined values.
+ * It is displayed by a {@link UiListAttributeNode}.
+ *
+ * Note: in Android resources, a "flag" is a list of fixed values where one or
+ * more values can be selected using an "or", e.g. "align='left|top'".
+ * By contrast, an "enum" is a list of fixed values of which only one can be
+ * selected at a given time, e.g. "gravity='right'".
+ * <p/>
+ * This class handles the "flag" case.
+ * The "enum" case is done using {@link ListAttributeDescriptor}.
+ */
+public class FlagAttributeDescriptor extends TextAttributeDescriptor {
+
+ private String[] mNames;
+
+ /**
+ * Creates a new {@link FlagAttributeDescriptor}.
+ * <p/>
+ * If <code>attrInfo</code> is not null and has non-null flag values, these will be
+ * used for the list.
+ * Otherwise values are automatically extracted from the FrameworkResourceManager.
+ */
+ public FlagAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ if (attrInfo != null) {
+ mNames = attrInfo.getFlagValues();
+ }
+ }
+
+ /**
+ * Creates a new {@link FlagAttributeDescriptor} which uses the provided values
+ * and does not lookup the content of <code>attrInfo</code>.
+ */
+ public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
+ String tooltip, IAttributeInfo attrInfo, String[] names) {
+ super(xmlLocalName, nsUri, attrInfo);
+ mNames = names;
+ }
+
+ /**
+ * @return The initial names of the flags. Can be null, in which case the Framework
+ * resource parser should be checked.
+ */
+ public String[] getNames() {
+ return mNames;
+ }
+
+ /**
+ * @return A new {@link UiListAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiFlagAttributeNode(this, uiParent);
+ }
+
+ // ------- IPropertyDescriptor Methods
+
+ @Override
+ public CellEditor createPropertyEditor(Composite parent) {
+ return new FlagValueCellEditor(parent);
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IDescriptorProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IDescriptorProvider.java
new file mode 100644
index 000000000..860ed394e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IDescriptorProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+public interface IDescriptorProvider {
+
+ ElementDescriptor[] getRootElementDescriptors();
+
+ ElementDescriptor getDescriptor();
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ITextAttributeCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ITextAttributeCreator.java
new file mode 100755
index 000000000..1fc662364
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ITextAttributeCreator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 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.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.common.api.IAttributeInfo;
+
+
+/**
+ * The {@link ITextAttributeCreator} interface is used by the appendAttribute(...) in
+ * {@link DescriptorsUtils} to allows callers to override the kind of
+ * {@link TextAttributeDescriptor} created for a given XML attribute name.
+ * <p/>
+ * The <code>create()</code> method must take arguments that are similar to the
+ * single constructor for {@link TextAttributeDescriptor}.
+ */
+public interface ITextAttributeCreator {
+
+ /**
+ * Creates a new {@link TextAttributeDescriptor} instance for the given XML name,
+ * UI name and tooltip.
+ *
+ * @param xmlLocalName The XML name of the attribute (case sensitive)
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ * @return A new {@link TextAttributeDescriptor} (or derived) instance.
+ */
+ public TextAttributeDescriptor create(
+ String xmlLocalName,
+ String nsUri,
+ IAttributeInfo attrInfo);
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IUnknownDescriptorProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IUnknownDescriptorProvider.java
new file mode 100755
index 000000000..931c1b726
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/IUnknownDescriptorProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 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.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.values.uimodel.UiItemElementNode;
+
+/**
+ * {@link UiItemElementNode} is the main class that creates the UI Model hierarchy based
+ * on an XML DOM hierarchy, matching XML names to the {@link ElementDescriptor} names.
+ * <p/>
+ * This interface declares a provider that can provide an {@link ElementDescriptor}
+ * for an unknown XML local name.
+ */
+public interface IUnknownDescriptorProvider {
+
+ /**
+ * Returns an instance of {@link ElementDescriptor} matching the given XML Local Name.
+ *
+ * @param xmlLocalName The XML local name.
+ * @return A new or existing {@link ElementDescriptor} or derived instance. Must not be null.
+ */
+ ElementDescriptor getDescriptor(String xmlLocalName);
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ListAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ListAttributeDescriptor.java
new file mode 100644
index 000000000..16b0d55f1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ListAttributeDescriptor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.ui.ListValueCellEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiListAttributeNode;
+
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Describes a text attribute that can contains some predefined values.
+ * It is displayed by a {@link UiListAttributeNode}.
+ */
+public class ListAttributeDescriptor extends TextAttributeDescriptor {
+
+ private String[] mValues = null;
+
+ /**
+ * Used by {@link DescriptorsUtils} to create instances of this descriptor.
+ */
+ public static final ITextAttributeCreator CREATOR = new ITextAttributeCreator() {
+ @Override
+ public TextAttributeDescriptor create(String xmlLocalName,
+ String nsUri, IAttributeInfo attrInfo) {
+ return new ListAttributeDescriptor(xmlLocalName, nsUri, attrInfo);
+ }
+ };
+
+ /**
+ * Creates a new {@link ListAttributeDescriptor}.
+ * <p/>
+ * If <code>attrInfo</code> is not null and has non-null enum values, these will be
+ * used for the list.
+ * Otherwise values are automatically extracted from the FrameworkResourceManager.
+ */
+ public ListAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ if (attrInfo != null) {
+ mValues = attrInfo.getEnumValues();
+ }
+ }
+
+ /**
+ * Creates a new {@link ListAttributeDescriptor} which uses the provided values
+ * and does not lookup the content of <code>attrInfo</code>.
+ */
+ public ListAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo,
+ String[] values) {
+ super(xmlLocalName, nsUri, attrInfo);
+ mValues = values;
+ }
+
+ public String[] getValues() {
+ return mValues;
+ }
+
+ /**
+ * @return A new {@link UiListAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiListAttributeNode(this, uiParent);
+ }
+
+ // ------- IPropertyDescriptor Methods
+
+ @Override
+ public CellEditor createPropertyEditor(Composite parent) {
+ return new ListValueCellEditor(parent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ReferenceAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ReferenceAttributeDescriptor.java
new file mode 100644
index 000000000..0f146c198
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ReferenceAttributeDescriptor.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.ui.ResourceValueCellEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
+import com.android.resources.ResourceType;
+
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Describes an XML attribute displayed containing a value or a reference to a resource.
+ * It is displayed by a {@link UiResourceAttributeNode}.
+ */
+public final class ReferenceAttributeDescriptor extends TextAttributeDescriptor {
+
+ /**
+ * The {@link ResourceType} that this reference attribute can accept. It can be null,
+ * in which case any reference type can be used.
+ */
+ private ResourceType mResourceType;
+
+ /**
+ * Used by {@link DescriptorsUtils} to create instances of this descriptor.
+ */
+ public static final ITextAttributeCreator CREATOR = new ITextAttributeCreator() {
+ @Override
+ public TextAttributeDescriptor create(String xmlLocalName,
+ String nsUri, IAttributeInfo attrInfo) {
+ return new ReferenceAttributeDescriptor(
+ ResourceType.DRAWABLE,
+ xmlLocalName, nsUri,
+ new AttributeInfo(xmlLocalName, Format.REFERENCE_SET));
+ }
+ };
+
+ /**
+ * Creates a reference attributes that can contain any type of resources.
+ * @param xmlLocalName The XML name of the attribute (case sensitive)
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ */
+ public ReferenceAttributeDescriptor(String xmlLocalName, String nsUri,
+ IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * Creates a reference attributes that can contain a reference to a specific
+ * {@link ResourceType}.
+ * @param resourceType The specific {@link ResourceType} that this reference attribute supports.
+ * It can be <code>null</code>, in which case, all resource types are supported.
+ * @param xmlLocalName The XML name of the attribute (case sensitive)
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ */
+ public ReferenceAttributeDescriptor(ResourceType resourceType,
+ String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ mResourceType = resourceType;
+ }
+
+
+ /** Returns the {@link ResourceType} that this reference attribute can accept.
+ * It can be null, in which case any reference type can be used. */
+ public ResourceType getResourceType() {
+ return mResourceType;
+ }
+
+ /**
+ * @return A new {@link UiResourceAttributeNode} linked to this reference descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiResourceAttributeNode(mResourceType, this, uiParent);
+ }
+
+ // ------- IPropertyDescriptor Methods
+
+ @Override
+ public CellEditor createPropertyEditor(Composite parent) {
+ return new ResourceValueCellEditor(parent);
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/SeparatorAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/SeparatorAttributeDescriptor.java
new file mode 100644
index 000000000..034bf8eb0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/SeparatorAttributeDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 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.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiSeparatorAttributeNode;
+
+/**
+ * {@link SeparatorAttributeDescriptor} does not represent any real attribute.
+ * <p/>
+ * It is used to separate groups of attributes visually.
+ */
+public class SeparatorAttributeDescriptor extends AttributeDescriptor {
+
+ /**
+ * Creates a new {@link SeparatorAttributeDescriptor}
+ */
+ public SeparatorAttributeDescriptor(String label) {
+ super(label /* xmlLocalName */, null /* nsUri */, null /* info */);
+ }
+
+ /**
+ * @return A new {@link UiAttributeNode} linked to this descriptor or null if this
+ * attribute has no user interface.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiSeparatorAttributeNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextAttributeDescriptor.java
new file mode 100644
index 000000000..f8c7806ae
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextAttributeDescriptor.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+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.ui.TextValueCellEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;
+
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.views.properties.IPropertyDescriptor;
+
+import java.util.EnumSet;
+import java.util.Locale;
+
+
+/**
+ * Describes a textual XML attribute.
+ * <p/>
+ * Such an attribute has a tooltip and would typically be displayed by
+ * {@link UiTextAttributeNode} using a label widget and text field.
+ * <p/>
+ * This is the "default" kind of attribute. If in doubt, use this.
+ */
+public class TextAttributeDescriptor extends AttributeDescriptor implements IPropertyDescriptor {
+ public static final String DEPRECATED_CATEGORY = "Deprecated";
+
+ private String mUiName;
+ private String mTooltip;
+ private boolean mRequired;
+
+ /**
+ * Creates a new {@link TextAttributeDescriptor}
+ *
+ * @param xmlLocalName The XML name of the attribute (case sensitive)
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ */
+ public TextAttributeDescriptor(
+ String xmlLocalName,
+ String nsUri,
+ IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * @return The UI name of the attribute. Cannot be an empty string and cannot be null.
+ */
+ @NonNull
+ public String getUiName() {
+ if (mUiName == null) {
+ IAttributeInfo info = getAttributeInfo();
+ if (info != null) {
+ mUiName = DescriptorsUtils.prettyAttributeUiName(info.getName());
+ if (mRequired) {
+ mUiName += "*"; //$NON-NLS-1$
+ }
+ } else {
+ mUiName = getXmlLocalName();
+ }
+ }
+
+ return mUiName;
+ }
+
+
+ /**
+ * Sets the UI name to be associated with this descriptor. This is usually
+ * computed lazily from the {@link #getAttributeInfo()} data, but for some
+ * hardcoded/builtin descriptor this is manually initialized.
+ *
+ * @param uiName the new UI name to be used
+ * @return this, for constructor setter chaining
+ */
+ public TextAttributeDescriptor setUiName(String uiName) {
+ mUiName = uiName;
+
+ return this;
+ }
+
+ /**
+ * Sets the tooltip to be associated with this descriptor. This is usually
+ * computed lazily from the {@link #getAttributeInfo()} data, but for some
+ * hardcoded/builtin descriptor this is manually initialized.
+ *
+ * @param tooltip the new tooltip to be used
+ * @return this, for constructor setter chaining
+ */
+ public TextAttributeDescriptor setTooltip(String tooltip) {
+ mTooltip = tooltip;
+
+ return this;
+ }
+
+ /**
+ * Sets whether this attribute is required
+ *
+ * @param required whether this attribute is required
+ * @return this, for constructor setter chaining
+ */
+ public TextAttributeDescriptor setRequired(boolean required) {
+ mRequired = required;
+
+ return this;
+ }
+
+ /**
+ * Returns whether this attribute is required
+ *
+ * @return whether this attribute is required
+ */
+ public boolean isRequired() {
+ return mRequired;
+ }
+
+ /**
+ * The tooltip string is either null or a non-empty string.
+ * <p/>
+ * The tooltip is based on the Javadoc of the attribute and already processed via
+ * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as
+ * a UI tooltip.
+ * <p/>
+ * An empty string is converted to null, to match the behavior of setToolTipText() in
+ * {@link Control}.
+ *
+ * @return A non-empty tooltip string or null
+ */
+ @Nullable
+ public String getTooltip() {
+ if (mTooltip == null) {
+ IAttributeInfo info = getAttributeInfo();
+ if (info == null) {
+ mTooltip = "";
+ return mTooltip;
+ }
+
+ String tooltip = null;
+ String rawTooltip = info.getJavaDoc();
+ if (rawTooltip == null) {
+ rawTooltip = "";
+ }
+
+ String deprecated = info.getDeprecatedDoc();
+ if (deprecated != null) {
+ if (rawTooltip.length() > 0) {
+ rawTooltip += "@@"; //$NON-NLS-1$ insert a break
+ }
+ rawTooltip += "* Deprecated";
+ if (deprecated.length() != 0) {
+ rawTooltip += ": " + deprecated; //$NON-NLS-1$
+ }
+ if (deprecated.length() == 0 || !deprecated.endsWith(".")) { //$NON-NLS-1$
+ rawTooltip += "."; //$NON-NLS-1$
+ }
+ }
+
+ // Add the known types to the tooltip
+ EnumSet<Format> formats_list = info.getFormats();
+ int flen = formats_list.size();
+ if (flen > 0) {
+ StringBuilder sb = new StringBuilder();
+ if (rawTooltip != null && rawTooltip.length() > 0) {
+ sb.append(rawTooltip);
+ sb.append(" "); //$NON-NLS-1$
+ }
+ if (sb.length() > 0) {
+ sb.append("@@"); //$NON-NLS-1$ @@ inserts a break before the types
+ }
+ sb.append("["); //$NON-NLS-1$
+ boolean isFirst = true;
+ for (Format f : formats_list) {
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(f.toString().toLowerCase(Locale.US));
+ }
+ // The extra space at the end makes the tooltip more readable on Windows.
+ sb.append("]"); //$NON-NLS-1$
+
+ if (mRequired) {
+ // Note: this string is split in 2 to make it translatable.
+ sb.append(".@@"); //$NON-NLS-1$ @@ inserts a break and is not translatable
+ sb.append("* Required.");
+ }
+
+ // The extra space at the end makes the tooltip more readable on Windows.
+ sb.append(" "); //$NON-NLS-1$
+
+ rawTooltip = sb.toString();
+ tooltip = DescriptorsUtils.formatTooltip(rawTooltip);
+ }
+
+ if (tooltip == null) {
+ tooltip = DescriptorsUtils.formatTooltip(rawTooltip);
+ }
+ mTooltip = tooltip;
+ }
+
+ return mTooltip.isEmpty() ? null : mTooltip;
+ }
+
+ /**
+ * @return A new {@link UiTextAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiTextAttributeNode(this, uiParent);
+ }
+
+ // ------- IPropertyDescriptor Methods
+
+ @Override
+ public CellEditor createPropertyEditor(Composite parent) {
+ return new TextValueCellEditor(parent);
+ }
+
+ @Override
+ public String getCategory() {
+ if (isDeprecated()) {
+ return DEPRECATED_CATEGORY;
+ }
+
+ ElementDescriptor parent = getParent();
+ if (parent != null) {
+ return parent.getUiName();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return getTooltip();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return getUiName();
+ }
+
+ @Override
+ public String[] getFilterFlags() {
+ return null;
+ }
+
+ @Override
+ public Object getHelpContextIds() {
+ return null;
+ }
+
+ @Override
+ public Object getId() {
+ return this;
+ }
+
+ @Override
+ public ILabelProvider getLabelProvider() {
+ return AttributeDescriptorLabelProvider.getProvider();
+ }
+
+ @Override
+ public boolean isCompatibleWith(IPropertyDescriptor anotherProperty) {
+ return anotherProperty == this;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextValueDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextValueDescriptor.java
new file mode 100644
index 000000000..6bfe4c778
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/TextValueDescriptor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextValueNode;
+
+
+/**
+ * Describes the value of an XML element.
+ * <p/>
+ * The value is a simple text string, displayed by an {@link UiTextValueNode}.
+ */
+public class TextValueDescriptor extends TextAttributeDescriptor {
+
+ /**
+ * Creates a new {@link TextValueDescriptor}
+ *
+ * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null.
+ * @param tooltip A non-empty tooltip string or null
+ */
+ public TextValueDescriptor(String uiName, String tooltip) {
+ super("#text" /* xmlLocalName */, null /* nsUri */, null /* info */);
+ setUiName(uiName);
+ setTooltip(tooltip);
+ }
+
+ /**
+ * @return A new {@link UiTextValueNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiTextValueNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/XmlnsAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/XmlnsAttributeDescriptor.java
new file mode 100644
index 000000000..39bb0f5f8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/XmlnsAttributeDescriptor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 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.descriptors;
+
+import static com.android.SdkConstants.XMLNS;
+import static com.android.SdkConstants.XMLNS_URI;
+
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+
+/**
+ * Describes an XMLNS attribute that is hidden.
+ * <p/>
+ * Such an attribute has no user interface and no corresponding {@link UiAttributeNode}.
+ * It also has a single constant default value.
+ * <p/>
+ * When loading an XML, we'll ignore this attribute.
+ * However when writing a new XML, we should always write this attribute.
+ * <p/>
+ * Currently this is used for the xmlns:android attribute in the manifest element.
+ */
+public final class XmlnsAttributeDescriptor extends AttributeDescriptor {
+
+ private String mValue;
+
+ public XmlnsAttributeDescriptor(String defaultPrefix, String value) {
+ super(defaultPrefix, XMLNS_URI, null /* info */);
+ mValue = value;
+ }
+
+ /**
+ * Returns the value of this specialized attribute descriptor, which is the URI associated
+ * to the declared namespace prefix.
+ */
+ public String getValue() {
+ return mValue;
+ }
+
+ /**
+ * Returns the "xmlns" prefix that is always used by this node for its namespace URI.
+ * This is defined by the XML specification.
+ */
+ public String getXmlNsPrefix() {
+ return XMLNS;
+ }
+
+ /**
+ * Returns the fully-qualified attribute name, namely "xmlns:xxx" where xxx is
+ * the defaultPrefix passed in the constructor.
+ */
+ public String getXmlNsName() {
+ return getXmlNsPrefix() + ":" + getXmlLocalName(); //$NON-NLS-1$
+ }
+
+ /**
+ * @return Always returns null. {@link XmlnsAttributeDescriptor} has no user interface.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return null;
+ }
+}