diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors')
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; + } +} |