diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt')
16 files changed, 632 insertions, 211 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java index ab64ef1..c0a8f53 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java @@ -160,7 +160,7 @@ public class BaseLayoutRule extends BaseViewRule { // Generate list of possible gravity value constants assert IAttributeInfo.Format.FLAG.in(info.getFormats()); for (String name : info.getFlagValues()) { - titles.add(prettyName(name)); + titles.add(getAttributeDisplayName(name)); ids.add(name); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java index 66688d9..dcf0f14 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java @@ -17,10 +17,14 @@ package com.android.ide.common.layout; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT; import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.common.layout.LayoutConstants.DOT_LAYOUT_PARAMS; import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; @@ -36,6 +40,7 @@ import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.IValidator; +import com.android.ide.common.api.IViewMetadata; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; import com.android.ide.common.api.Point; @@ -43,8 +48,8 @@ import com.android.ide.common.api.Rect; import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.RuleAction.ActionProvider; import com.android.ide.common.api.RuleAction.ChoiceProvider; -import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.SegmentType; +import com.android.resources.ResourceType; import com.android.util.Pair; import java.net.URL; @@ -55,6 +60,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -64,16 +70,17 @@ import java.util.Set; * Common IViewRule processing to all view and layout classes. */ public class BaseViewRule implements IViewRule { + /** List of recently edited properties */ + private static List<String> sRecent = new LinkedList<String>(); + + /** Maximum number of recent properties to track and list */ + private final static int MAX_RECENT_COUNT = 12; + // Strings used as internal ids, group ids and prefixes for actions private static final String FALSE_ID = "false"; //$NON-NLS-1$ private static final String TRUE_ID = "true"; //$NON-NLS-1$ private static final String PROP_PREFIX = "@prop@"; //$NON-NLS-1$ private static final String CLEAR_ID = "clear"; //$NON-NLS-1$ - private static final String PROPERTIES_ID = "properties"; //$NON-NLS-1$ - private static final String EDIT_TEXT_ID = "edittext"; //$NON-NLS-1$ - private static final String EDIT_ID_ID = "editid"; //$NON-NLS-1$ - private static final String WIDTH_ID = "layout_width"; //$NON-NLS-1$ - private static final String HEIGHT_ID = "layout_height"; //$NON-NLS-1$ private static final String ZCUSTOM = "zcustom"; //$NON-NLS-1$ protected IClientRulesEngine mRulesEngine; @@ -159,7 +166,7 @@ public class BaseViewRule implements IViewRule { final String actionId = isProp ? fullActionId.substring(PROP_PREFIX.length()) : fullActionId; - if (fullActionId.equals(WIDTH_ID)) { + if (fullActionId.equals(ATTR_LAYOUT_WIDTH)) { final String newAttrValue = getValue(valueId, newWidth); if (newAttrValue != null) { for (INode node : selectedNodes) { @@ -167,9 +174,10 @@ public class BaseViewRule implements IViewRule { new PropertySettingNodeHandler(ANDROID_URI, ATTR_LAYOUT_WIDTH, newAttrValue)); } + editedProperty(ATTR_LAYOUT_WIDTH); } return; - } else if (fullActionId.equals(HEIGHT_ID)) { + } else if (fullActionId.equals(ATTR_LAYOUT_HEIGHT)) { // Ask the user final String newAttrValue = getValue(valueId, newHeight); if (newAttrValue != null) { @@ -178,9 +186,10 @@ public class BaseViewRule implements IViewRule { new PropertySettingNodeHandler(ANDROID_URI, ATTR_LAYOUT_HEIGHT, newAttrValue)); } + editedProperty(ATTR_LAYOUT_HEIGHT); } return; - } else if (fullActionId.equals(EDIT_ID_ID)) { + } else if (fullActionId.equals(ATTR_ID)) { // Ids must be set individually so open the id dialog for each // selected node (though allow cancel to break the loop) for (INode node : selectedNodes) { @@ -195,80 +204,91 @@ public class BaseViewRule implements IViewRule { } node.editXml("Change ID", new PropertySettingNodeHandler(ANDROID_URI, ATTR_ID, newId)); + editedProperty(ATTR_ID); } else if (newId == null) { // Cancelled break; } } return; - } else { + } else if (isProp) { INode firstNode = selectedNodes.get(0); - if (fullActionId.equals(EDIT_TEXT_ID)) { - String oldText = selectedNodes.size() == 1 - ? firstNode.getStringAttr(ANDROID_URI, ATTR_TEXT) - : ""; //$NON-NLS-1$ - oldText = ensureValidString(oldText); - String newText = mRulesEngine.displayResourceInput("string", oldText); //$NON-NLS-1$ - if (newText != null) { - for (INode node : selectedNodes) { - node.editXml("Change Text", - new PropertySettingNodeHandler(ANDROID_URI, - ATTR_TEXT, newText.length() > 0 ? newText : null)); + String key = getPropertyMapKey(selectedNode); + Map<String, Prop> props = mAttributesMap.get(key); + final Prop prop = (props != null) ? props.get(actionId) : null; + + if (prop != null) { + editedProperty(actionId); + + // For custom values (requiring an input dialog) input the + // value outside the undo-block. + // Input the value as a text, unless we know it's the "text" or + // "style" attributes (where we know we want to ask for specific + // resource types). + String uri = ANDROID_URI; + String v = null; + if (prop.isStringEdit()) { + boolean isStyle = actionId.equals(ATTR_STYLE); + boolean isText = actionId.equals(ATTR_TEXT); + boolean isHint = actionId.equals(ATTR_HINT); + if (isStyle || isText || isHint) { + String resourceTypeName = isStyle + ? ResourceType.STYLE.getName() + : ResourceType.STRING.getName(); + String oldValue = selectedNodes.size() == 1 + ? firstNode.getStringAttr(null, ATTR_STYLE) + : ""; //$NON-NLS-1$ + oldValue = ensureValidString(oldValue); + v = mRulesEngine.displayResourceInput(resourceTypeName, oldValue); + if (isStyle) { + uri = null; + } + } else { + v = inputAttributeValue(firstNode, actionId); } } - return; - } else if (isProp) { - String key = getPropertyMapKey(selectedNode); - Map<String, Prop> props = mAttributesMap.get(key); - final Prop prop = (props != null) ? props.get(actionId) : null; - - if (prop != null) { - // For custom values (requiring an input dialog) input the - // value outside the undo-block - final String customValue = prop.isStringEdit() - ? inputAttributeValue(firstNode, actionId) : null; - - for (INode n : selectedNodes) { - if (prop.isToggle()) { - // case of toggle - String value = ""; //$NON-NLS-1$ - if (valueId.equals(TRUE_ID)) { - value = newValue ? "true" : ""; //$NON-NLS-1$ //$NON-NLS-2$ - } else if (valueId.equals(FALSE_ID)) { - value = newValue ? "false" : "";//$NON-NLS-1$ //$NON-NLS-2$ - } - n.setAttribute(ANDROID_URI, actionId, value); - } else if (prop.isFlag()) { - // case of a flag - String values = ""; //$NON-NLS-1$ - if (!valueId.equals(CLEAR_ID)) { - values = n.getStringAttr(ANDROID_URI, actionId); - Set<String> newValues = new HashSet<String>(); - if (values != null) { - newValues.addAll(Arrays.asList( - values.split("\\|"))); //$NON-NLS-1$ - } - if (newValue) { - newValues.add(valueId); - } else { - newValues.remove(valueId); - } - values = join('|', newValues); - } - n.setAttribute(ANDROID_URI, actionId, values); - } else if (prop.isEnum()) { - // case of an enum - String value = ""; //$NON-NLS-1$ - if (!valueId.equals(CLEAR_ID)) { - value = newValue ? valueId : ""; //$NON-NLS-1$ + final String customValue = v; + + for (INode n : selectedNodes) { + if (prop.isToggle()) { + // case of toggle + String value = ""; //$NON-NLS-1$ + if (valueId.equals(TRUE_ID)) { + value = newValue ? "true" : ""; //$NON-NLS-1$ //$NON-NLS-2$ + } else if (valueId.equals(FALSE_ID)) { + value = newValue ? "false" : "";//$NON-NLS-1$ //$NON-NLS-2$ + } + n.setAttribute(uri, actionId, value); + } else if (prop.isFlag()) { + // case of a flag + String values = ""; //$NON-NLS-1$ + if (!valueId.equals(CLEAR_ID)) { + values = n.getStringAttr(ANDROID_URI, actionId); + Set<String> newValues = new HashSet<String>(); + if (values != null) { + newValues.addAll(Arrays.asList( + values.split("\\|"))); //$NON-NLS-1$ } - n.setAttribute(ANDROID_URI, actionId, value); - } else { - assert prop.isStringEdit(); - // We've already received the value outside the undo block - if (customValue != null) { - n.setAttribute(ANDROID_URI, actionId, customValue); + if (newValue) { + newValues.add(valueId); + } else { + newValues.remove(valueId); } + values = join('|', newValues); + } + n.setAttribute(uri, actionId, values); + } else if (prop.isEnum()) { + // case of an enum + String value = ""; //$NON-NLS-1$ + if (!valueId.equals(CLEAR_ID)) { + value = newValue ? valueId : ""; //$NON-NLS-1$ + } + n.setAttribute(uri, actionId, value); + } else { + assert prop.isStringEdit(); + // We've already received the value outside the undo block + if (customValue != null) { + n.setAttribute(uri, actionId, customValue); } } } @@ -332,13 +352,16 @@ public class BaseViewRule implements IViewRule { IAttributeInfo textAttribute = selectedNode.getAttributeInfo(ANDROID_URI, ATTR_TEXT); if (textAttribute != null) { - actions.add(RuleAction.createAction(EDIT_TEXT_ID, "Edit Text...", onChange, + actions.add(RuleAction.createAction(PROP_PREFIX + ATTR_TEXT, "Edit Text...", onChange, null, 10, true)); } - actions.add(RuleAction.createAction(EDIT_ID_ID, "Edit ID...", onChange, null, 20, true)); + actions.add(RuleAction.createAction(ATTR_ID, "Edit ID...", onChange, null, 20, true)); + + addCommonPropertyActions(actions, selectedNode, onChange, 21); // Create width choice submenu + actions.add(RuleAction.createSeparator(32)); List<Pair<String, String>> widthChoices = new ArrayList<Pair<String,String>>(4); widthChoices.add(Pair.of(VALUE_WRAP_CONTENT, "Wrap Content")); if (canMatchParent) { @@ -351,11 +374,11 @@ public class BaseViewRule implements IViewRule { } widthChoices.add(Pair.of(ZCUSTOM, "Other...")); actions.add(RuleAction.createChoices( - WIDTH_ID, "Layout Width", + ATTR_LAYOUT_WIDTH, "Layout Width", onChange, null /* iconUrls */, currentWidth, - null, 30, + null, 35, true, // supportsMultipleNodes widthChoices)); @@ -372,7 +395,7 @@ public class BaseViewRule implements IViewRule { } heightChoices.add(Pair.of(ZCUSTOM, "Other...")); actions.add(RuleAction.createChoices( - HEIGHT_ID, "Layout Height", + ATTR_LAYOUT_HEIGHT, "Layout Height", onChange, null /* iconUrls */, currentHeight, @@ -381,14 +404,52 @@ public class BaseViewRule implements IViewRule { heightChoices)); actions.add(RuleAction.createSeparator(45)); - RuleAction properties = RuleAction.createChoices(PROPERTIES_ID, "Properties", + RuleAction properties = RuleAction.createChoices("properties", "Other Properties", //$NON-NLS-1$ onChange /*callback*/, null /*icon*/, 50, true /*supportsMultipleNodes*/, new ActionProvider() { public List<RuleAction> getNestedActions(INode node) { - List<RuleAction> propertyActions = createPropertyActions(node, - getPropertyMapKey(node), onChange); + List<RuleAction> propertyActionTypes = new ArrayList<RuleAction>(); + propertyActionTypes.add(RuleAction.createChoices( + "recent", "Recent", //$NON-NLS-1$ + onChange /*callback*/, null /*icon*/, 10, + true /*supportsMultipleNodes*/, new ActionProvider() { + public List<RuleAction> getNestedActions(INode n) { + List<RuleAction> propertyActions = new ArrayList<RuleAction>(); + addRecentPropertyActions(propertyActions, n, onChange); + return propertyActions; + } + })); + + propertyActionTypes.add(RuleAction.createSeparator(20)); + + addInheritedProperties(propertyActionTypes, node, onChange, 30); - return propertyActions; + propertyActionTypes.add(RuleAction.createSeparator(50)); + propertyActionTypes.add(RuleAction.createChoices( + "layoutparams", "Layout Parameters", //$NON-NLS-1$ + onChange /*callback*/, null /*icon*/, 60, + true /*supportsMultipleNodes*/, new ActionProvider() { + public List<RuleAction> getNestedActions(INode n) { + List<RuleAction> propertyActions = new ArrayList<RuleAction>(); + addPropertyActions(propertyActions, n, onChange, null, true); + return propertyActions; + } + })); + + propertyActionTypes.add(RuleAction.createSeparator(70)); + + propertyActionTypes.add(RuleAction.createChoices( + "allprops", "All By Name", //$NON-NLS-1$ + onChange /*callback*/, null /*icon*/, 80, + true /*supportsMultipleNodes*/, new ActionProvider() { + public List<RuleAction> getNestedActions(INode n) { + List<RuleAction> propertyActions = new ArrayList<RuleAction>(); + addPropertyActions(propertyActions, n, onChange, null, false); + return propertyActions; + } + })); + + return propertyActionTypes; } }); @@ -409,13 +470,201 @@ public class BaseViewRule implements IViewRule { } /** + * Adds menu items for the inherited attributes, one pull-right menu for each super class + * that defines attributes. + * + * @param propertyActionTypes the actions list to add into + * @param node the node to apply the attributes to + * @param onChange the callback to use for setting attributes + * @param sortPriority the initial sort attribute for the first menu item + */ + private void addInheritedProperties(List<RuleAction> propertyActionTypes, INode node, + final IMenuCallback onChange, int sortPriority) { + List<String> attributeSources = node.getAttributeSources(); + for (final String definedBy : attributeSources) { + String sourceClass = definedBy; + + // Strip package prefixes when necessary + int index = sourceClass.length(); + if (sourceClass.endsWith(DOT_LAYOUT_PARAMS)) { + index = sourceClass.length() - DOT_LAYOUT_PARAMS.length() - 1; + } + int lastDot = sourceClass.lastIndexOf('.', index); + if (lastDot != -1) { + sourceClass = sourceClass.substring(lastDot + 1); + } + + String label; + if (definedBy.equals(node.getFqcn())) { + label = String.format("Defined by %1$s", sourceClass); + } else { + label = String.format("Inherited from %1$s", sourceClass); + } + + propertyActionTypes.add(RuleAction.createChoices("def_" + definedBy, + label, + onChange /*callback*/, null /*icon*/, sortPriority++, + true /*supportsMultipleNodes*/, new ActionProvider() { + public List<RuleAction> getNestedActions(INode n) { + List<RuleAction> propertyActions = new ArrayList<RuleAction>(); + addPropertyActions(propertyActions, n, onChange, definedBy, false); + return propertyActions; + } + })); + } + } + + /** + * Creates a list of properties that are commonly edited for views of the + * selected node's type + */ + private void addCommonPropertyActions(List<RuleAction> actions, INode selectedNode, + IMenuCallback onChange, int sortPriority) { + Map<String, Prop> properties = getPropertyMetadata(selectedNode); + IViewMetadata metadata = mRulesEngine.getMetadata(selectedNode.getFqcn()); + if (metadata != null) { + List<String> attributes = metadata.getTopAttributes(); + if (attributes.size() > 0) { + for (String attribute : attributes) { + // Text and ID are handled manually in the menu construction code because + // we want to place them consistently and customize the action label + if (ATTR_TEXT.equals(attribute) || ATTR_ID.equals(attribute)) { + continue; + } + + Prop property = properties.get(attribute); + if (property != null) { + String title = property.getTitle(); + if (title.endsWith("...")) { + title = String.format("Edit %1$s", property.getTitle()); + } + actions.add(createPropertyAction(property, attribute, title, + selectedNode, onChange, sortPriority)); + sortPriority++; + } + } + } + } + } + + /** + * Record that the given property was just edited; adds it to the front of + * the recently edited property list + * + * @param property the name of the property + */ + static void editedProperty(String property) { + if (sRecent.contains(property)) { + sRecent.remove(property); + } else if (sRecent.size() > MAX_RECENT_COUNT) { + sRecent.remove(sRecent.size() - 1); + } + sRecent.add(0, property); + } + + /** + * Creates a list of recently modified properties that apply to the given selected node + */ + private void addRecentPropertyActions(List<RuleAction> actions, INode selectedNode, + IMenuCallback onChange) { + int sortPriority = 10; + Map<String, Prop> properties = getPropertyMetadata(selectedNode); + for (String attribute : sRecent) { + Prop property = properties.get(attribute); + if (property != null) { + actions.add(createPropertyAction(property, attribute, property.getTitle(), + selectedNode, onChange, sortPriority)); + sortPriority += 10; + } + } + } + + /** * Creates a list of nested actions representing the property-setting * actions for the given selected node */ - private List<RuleAction> createPropertyActions(final INode selectedNode, final String key, - final IMenuCallback onChange) { - List<RuleAction> propertyActions = new ArrayList<RuleAction>(); + private void addPropertyActions(List<RuleAction> actions, INode selectedNode, + IMenuCallback onChange, String definedBy, boolean layoutParamsOnly) { + + Map<String, Prop> properties = getPropertyMetadata(selectedNode); + + int sortPriority = 10; + for (Map.Entry<String, Prop> entry : properties.entrySet()) { + String id = entry.getKey(); + Prop property = entry.getValue(); + if (layoutParamsOnly) { + // If we have definedBy information, that is most accurate; all layout + // params will be defined by a class whose name ends with + // .LayoutParams: + if (definedBy != null) { + if (!definedBy.endsWith(DOT_LAYOUT_PARAMS)) { + continue; + } + } else if (!id.startsWith(ATTR_LAYOUT_PREFIX)) { + continue; + } + } + if (definedBy != null && !definedBy.equals(property.getDefinedBy())) { + continue; + } + actions.add(createPropertyAction(property, id, property.getTitle(), + selectedNode, onChange, sortPriority)); + sortPriority += 10; + } + + // The properties are coming out of map key order which isn't right, so sort + // alphabetically instead + Collections.sort(actions, new Comparator<RuleAction>() { + public int compare(RuleAction action1, RuleAction action2) { + return action1.getTitle().compareTo(action2.getTitle()); + } + }); + } + + private RuleAction createPropertyAction(Prop p, String id, String title, INode selectedNode, + IMenuCallback onChange, int sortPriority) { + if (p.isToggle()) { + // Toggles are handled as a multiple-choice between true, false + // and nothing (clear) + String value = selectedNode.getStringAttr(ANDROID_URI, id); + if (value != null) + value = value.toLowerCase(); + if ("true".equals(value)) { //$NON-NLS-1$ + value = TRUE_ID; + } else if ("false".equals(value)) { //$NON-NLS-1$ + value = FALSE_ID; + } else { + value = CLEAR_ID; + } + return RuleAction.createChoices(PROP_PREFIX + id, title, + onChange, BOOLEAN_CHOICE_PROVIDER, + value, + null, sortPriority, + true); + } else if (p.getChoices() != null) { + // Enum or flags. Their possible values are the multiple-choice + // items, with an extra "clear" option to remove everything. + String current = selectedNode.getStringAttr(ANDROID_URI, id); + if (current == null || current.length() == 0) { + current = CLEAR_ID; + } + return RuleAction.createChoices(PROP_PREFIX + id, title, + onChange, new EnumPropertyChoiceProvider(p), + current, + null, sortPriority, + true); + } else { + return RuleAction.createAction( + PROP_PREFIX + id, + title, + onChange, + null, sortPriority, + true); + } + } + private Map<String, Prop> getPropertyMetadata(final INode selectedNode) { + String key = getPropertyMapKey(selectedNode); Map<String, Prop> props = mAttributesMap.get(key); if (props == null) { // Prepare the property map @@ -431,91 +680,38 @@ public class BaseViewRule implements IViewRule { continue; } - String title = prettyName(id); + String title = getAttributeDisplayName(id); + String definedBy = attrInfo != null ? attrInfo.getDefinedBy() : null; if (IAttributeInfo.Format.BOOLEAN.in(formats)) { - props.put(id, new Prop(title, true)); + props.put(id, new Prop(title, true, definedBy)); } else if (IAttributeInfo.Format.ENUM.in(formats)) { // Convert each enum into a map id=>title Map<String, String> values = new HashMap<String, String>(); if (attrInfo != null) { for (String e : attrInfo.getEnumValues()) { - values.put(e, prettyName(e)); + values.put(e, getAttributeDisplayName(e)); } } - props.put(id, new Prop(title, false, false, values)); + props.put(id, new Prop(title, false, false, values, definedBy)); } else if (IAttributeInfo.Format.FLAG.in(formats)) { // Convert each flag into a map id=>title Map<String, String> values = new HashMap<String, String>(); if (attrInfo != null) { for (String e : attrInfo.getFlagValues()) { - values.put(e, prettyName(e)); + values.put(e, getAttributeDisplayName(e)); } } - props.put(id, new Prop(title, false, true, values)); + props.put(id, new Prop(title, false, true, values, definedBy)); } else { - props.put(id, new Prop(title + "...", false)); + props.put(id, new Prop(title + "...", false, definedBy)); } } mAttributesMap.put(key, props); } - - int nextPriority = 10; - for (Map.Entry<String, Prop> entry : props.entrySet()) { - String id = entry.getKey(); - Prop p = entry.getValue(); - if (p.isToggle()) { - // Toggles are handled as a multiple-choice between true, false - // and nothing (clear) - String value = selectedNode.getStringAttr(ANDROID_URI, id); - if (value != null) - value = value.toLowerCase(); - if ("true".equals(value)) { //$NON-NLS-1$ - value = TRUE_ID; - } else if ("false".equals(value)) { //$NON-NLS-1$ - value = FALSE_ID; - } else { - value = CLEAR_ID; - } - Choices action = RuleAction.createChoices(PROP_PREFIX + id, p.getTitle(), - onChange, BOOLEAN_CHOICE_PROVIDER, - value, - null, nextPriority++, - true); - propertyActions.add(action); - } else if (p.getChoices() != null) { - // Enum or flags. Their possible values are the multiple-choice - // items, with an extra "clear" option to remove everything. - String current = selectedNode.getStringAttr(ANDROID_URI, id); - if (current == null || current.length() == 0) { - current = CLEAR_ID; - } - Choices action = RuleAction.createChoices(PROP_PREFIX + id, p.getTitle(), - onChange, new EnumPropertyChoiceProvider(p), - current, - null, nextPriority++, - true); - propertyActions.add(action); - } else { - RuleAction action = RuleAction.createAction( - PROP_PREFIX + id, - p.getTitle(), - onChange, - null, nextPriority++, - true); - propertyActions.add(action); - } - } - - // The properties are coming out of map key order which isn't right - Collections.sort(propertyActions, new Comparator<RuleAction>() { - public int compare(RuleAction action1, RuleAction action2) { - return action1.getTitle().compareTo(action2.getTitle()); - } - }); - return propertyActions; + return props; } /** @@ -627,9 +823,31 @@ public class BaseViewRule implements IViewRule { return map; } - public static String prettyName(String name) { + /** + * Produces a display name for an attribute, usually capitalizing the attribute name + * and splitting up underscores into new words + * + * @param name the attribute name to convert + * @return a display name for the attribute name + */ + public static String getAttributeDisplayName(String name) { if (name != null && name.length() > 0) { - name = Character.toUpperCase(name.charAt(0)) + name.substring(1).replace('_', ' '); + StringBuilder sb = new StringBuilder(); + boolean capitalizeNext = true; + for (int i = 0, n = name.length(); i < n; i++) { + char c = name.charAt(i); + if (capitalizeNext) { + c = Character.toUpperCase(c); + } + capitalizeNext = false; + if (c == '_') { + c = ' '; + capitalizeNext = true; + } + sb.append(c); + } + + return sb.toString(); } return name; @@ -698,16 +916,23 @@ public class BaseViewRule implements IViewRule { private final boolean mFlag; private final String mTitle; private final Map<String, String> mChoices; + private String mDefinedBy; + + public Prop(String title, boolean isToggle, boolean isFlag, Map<String, String> choices, + String definedBy) { + mTitle = title; + mToggle = isToggle; + mFlag = isFlag; + mChoices = choices; + mDefinedBy = definedBy; + } - public Prop(String title, boolean isToggle, boolean isFlag, Map<String, String> choices) { - this.mTitle = title; - this.mToggle = isToggle; - this.mFlag = isFlag; - this.mChoices = choices; + public String getDefinedBy() { + return mDefinedBy; } - public Prop(String title, boolean isToggle) { - this(title, isToggle, false, null); + public Prop(String title, boolean isToggle, String definedBy) { + this(title, isToggle, false, null, definedBy); } private boolean isToggle() { @@ -760,6 +985,12 @@ public class BaseViewRule implements IViewRule { public void onRemovingChildren(List<INode> deleted, INode parent) { } + /** + * Strips the {@code @+id} or {@code @id} prefix off of the given id + * + * @param id attribute to be stripped + * @return the id name without the {@code @+id} or {@code @id} prefix + */ public static String stripIdPrefix(String id) { if (id == null) { return ""; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java index e26df79..a87de29 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java @@ -76,6 +76,7 @@ public class EditTextRule extends BaseViewRule { actions.add(RuleAction.createAction("_setfocus", label, onChange, //$NON-NLS-1$ null, 5, false /*supportsMultipleNodes*/)); + actions.add(RuleAction.createSeparator(7)); } /** Returns true if the given node currently has focus */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index d4ed864..7b6081d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -176,6 +176,9 @@ public class LayoutConstants { /** The android.webkit. package prefix */ public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; //$NON-NLS-1$ + /** The LayoutParams inner-class name suffix, .LayoutParams */ + public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; //$NON-NLS-1$ + /** The fully qualified class name of an EditText view */ public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java index ae42fc3..3639648 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java @@ -44,14 +44,13 @@ import com.android.ide.common.api.IViewMetadata; import com.android.ide.common.api.IViewMetadata.FillPreference; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.SegmentType; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.sdklib.SdkConstants; -import com.android.util.Pair; import java.net.URL; import java.util.ArrayList; @@ -89,33 +88,6 @@ public class LinearLayoutRule extends BaseLayoutRule { LinearLayoutRule.class.getResource("allweight.png"); //$NON-NLS-1$ /** - * Add an explicit Orientation toggle to the context menu. - */ - @Override - public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { - super.addContextMenuActions(actions, selectedNode); - if (supportsOrientation()) { - String current = getCurrentOrientation(selectedNode); - IMenuCallback onChange = new PropertyCallback( - null, // use passed in nodes instead to support multiple nodes - "Change LinearLayout Orientation", - ANDROID_URI, ATTR_ORIENTATION); - List<Pair<String, String>> alternatives = new ArrayList<Pair<String,String>>(2); - alternatives.add(Pair.of("horizontal", "Horizontal")); //$NON-NLS-1$ - alternatives.add(Pair.of("vertical", "Vertical")); //$NON-NLS-1$ - RuleAction action = RuleAction.createChoices( - ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ - onChange, - null /* iconUrls */, - current, - null /* icon */, 5, true, - alternatives); - - actions.add(action); - } - } - - /** * Returns the current orientation, regardless of whether it has been defined in XML * * @param node The LinearLayout to look up the orientation for diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java index 6f58656..a761a0e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java @@ -38,6 +38,8 @@ public class AttributeInfo implements IAttributeInfo { private String mJavaDoc; /** Documentation for deprecated attributes. Null if not deprecated. */ private String mDeprecatedDoc; + /** The source class defining this attribute */ + private String mDefinedBy; /** * @param name The XML Name of the attribute @@ -117,4 +119,26 @@ public class AttributeInfo implements IAttributeInfo { public void setDeprecatedDoc(String deprecatedDoc) { mDeprecatedDoc = deprecatedDoc; } + + /** + * Sets the name of the class (fully qualified class name) which defined + * this attribute + * + * @param definedBy the name of the class (fully qualified class name) which + * defined this attribute + */ + public void setDefinedBy(String definedBy) { + mDefinedBy = definedBy; + } + + /** + * Returns the name of the class (fully qualified class name) which defined + * this attribute + * + * @return the name of the class (fully qualified class name) which defined + * this attribute + */ + public String getDefinedBy() { + return mDefinedBy; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java index f8d041c..ed2fb75 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java @@ -16,6 +16,8 @@ package com.android.ide.common.resources.platform; +import static com.android.ide.common.layout.LayoutConstants.DOT_LAYOUT_PARAMS; + import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.log.ILogger; import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo; @@ -162,7 +164,14 @@ public final class AttrsXmlParser { String xmlName = info.getShortClassName(); DeclareStyleableInfo style = mStyleMap.get(xmlName); if (style != null) { - info.setAttributes(style.getAttributes()); + String definedBy = info.getFullClassName(); + AttributeInfo[] attributes = style.getAttributes(); + for (AttributeInfo attribute : attributes) { + if (attribute.getDefinedBy() == null) { + attribute.setDefinedBy(definedBy); + } + } + info.setAttributes(attributes); info.setJavaDoc(style.getJavaDoc()); } } @@ -174,14 +183,24 @@ public final class AttrsXmlParser { public void loadLayoutParamsAttributes(LayoutParamsInfo info) { if (getDocument() != null) { // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout". + ViewClassInfo viewLayoutClass = info.getViewLayoutClass(); String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$ - info.getViewLayoutClass().getShortClassName(), + viewLayoutClass.getShortClassName(), info.getShortClassName()); xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$ DeclareStyleableInfo style = mStyleMap.get(xmlName); if (style != null) { - info.setAttributes(style.getAttributes()); + // For defined by, use the actual class name, e.g. + // android.widget.LinearLayout.LayoutParams + String definedBy = viewLayoutClass.getFullClassName() + DOT_LAYOUT_PARAMS; + AttributeInfo[] attributes = style.getAttributes(); + for (AttributeInfo attribute : attributes) { + if (attribute.getDefinedBy() == null) { + attribute.setDefinedBy(definedBy); + } + } + info.setAttributes(attributes); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/DeclareStyleableInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/DeclareStyleableInfo.java index 8719aa9..40111e2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/DeclareStyleableInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/DeclareStyleableInfo.java @@ -24,12 +24,12 @@ package com.android.ide.common.resources.platform; */ public class DeclareStyleableInfo { /** The style name, never null. */ - private String mStyleName; + private final String mStyleName; /** Attributes for this view or view group. Can be empty but never null. */ - private AttributeInfo[] mAttributes; + private final AttributeInfo[] mAttributes; /** Short javadoc. Can be null. */ private String mJavaDoc; - /** Optional name of the parents stylable. Can be null. */ + /** Optional name of the parents styleable. Can be null. */ private String[] mParents; /** @@ -70,7 +70,6 @@ public class DeclareStyleableInfo { } } - /** Returns style name */ public String getStyleName() { return mStyleName; @@ -81,11 +80,6 @@ public class DeclareStyleableInfo { return mAttributes; } - /** Sets the list of attributes for this View or ViewGroup. */ - public void setAttributes(AttributeInfo[] attributes) { - mAttributes = attributes; - } - /** Returns a short javadoc */ public String getJavaDoc() { return mJavaDoc; 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 index ce3d59a..7572120 100644 --- 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 @@ -296,7 +296,7 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { return mAttributes; } - /* Sets the list of allowed attributes. */ + /** Sets the list of allowed attributes. */ public void setAttributes(AttributeDescriptor[] attributes) { mAttributes = attributes; for (AttributeDescriptor attribute : attributes) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java index 3bfcb5c..ca0475d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java @@ -281,6 +281,7 @@ public final class LayoutDescriptors implements IDescriptorProvider { styleInfo, false, //required null); // overrides + styleInfo.setDefinedBy(SdkConstants.CLASS_VIEW); // Process all View attributes DescriptorsUtils.appendAttributes(attributes, @@ -290,11 +291,17 @@ public final class LayoutDescriptors implements IDescriptorProvider { null, // requiredAttributes null /* overrides */); + List<String> attributeSources = new ArrayList<String>(); + if (info.getAttributes() != null && info.getAttributes().length > 0) { + attributeSources.add(fqcn); + } + for (ViewClassInfo link = info.getSuperClass(); link != null; link = link.getSuperClass()) { AttributeInfo[] attrList = link.getAttributes(); if (attrList.length > 0) { + attributeSources.add(link.getFullClassName()); attributes.add(new SeparatorAttributeDescriptor( String.format("Attributes from %1$s", link.getShortClassName()))); DescriptorsUtils.appendAttributes(attributes, @@ -318,14 +325,16 @@ public final class LayoutDescriptors implements IDescriptorProvider { continue; } if (needSeparator) { + ViewClassInfo viewLayoutClass = layoutParams.getViewLayoutClass(); String title; + String shortClassName = viewLayoutClass.getShortClassName(); if (layoutParams.getShortClassName().equals( SdkConstants.CLASS_NAME_LAYOUTPARAMS)) { title = String.format("Layout Attributes from %1$s", - layoutParams.getViewLayoutClass().getShortClassName()); + shortClassName); } else { title = String.format("Layout Attributes from %1$s (%2$s)", - layoutParams.getViewLayoutClass().getShortClassName(), + shortClassName, layoutParams.getShortClassName()); } layoutAttributes.add(new SeparatorAttributeDescriptor(title)); @@ -350,6 +359,7 @@ public final class LayoutDescriptors implements IDescriptorProvider { layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), null, // children false /* mandatory */); + desc.setAttributeSources(Collections.unmodifiableList(attributeSources)); infoDescMap.put(info, desc); return desc; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java index a18b821..fdfe191 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java @@ -16,10 +16,11 @@ package com.android.ide.eclipse.adt.internal.editors.layout.descriptors; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; import static com.android.ide.common.layout.LayoutConstants.ANDROID_VIEW_PKG; import static com.android.ide.common.layout.LayoutConstants.ANDROID_WEBKIT_PKG; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -29,6 +30,9 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import org.eclipse.swt.graphics.Image; +import java.util.Collections; +import java.util.List; + /** * {@link ViewElementDescriptor} describes the properties expected for a given XML element node * representing a class in an XML Layout file. @@ -62,6 +66,9 @@ public class ViewElementDescriptor extends ElementDescriptor { /** The super-class descriptor. Can be null. */ private ViewElementDescriptor mSuperClassDesc; + /** List of attribute sources, classes that contribute attributes to {@link #mAttributes} */ + private List<String> mAttributeSources; + /** * Constructs a new {@link ViewElementDescriptor} based on its XML name, UI name, * the canonical name of the class it represents, its tooltip, its SDK url, its attributes list, @@ -110,12 +117,17 @@ public class ViewElementDescriptor extends ElementDescriptor { /** * Returns the fully qualified name of the View class represented by this element descriptor * e.g. "android.view.View". + * + * @return the fully qualified class name, never null */ public String getFullClassName() { return mFullClassName; } - /** Returns the list of layout attributes. Can be empty but not null. */ + /** Returns the list of layout attributes. Can be empty but not null. + * + * @return the list of layout attributes, never null + */ public AttributeDescriptor[] getLayoutAttributes() { return mLayoutAttributes; } @@ -141,6 +153,8 @@ public class ViewElementDescriptor extends ElementDescriptor { /** * Returns the {@link ViewElementDescriptor} of the super-class of this View descriptor * that matches the java View hierarchy. Can be null. + * + * @return the super class' descriptor or null */ public ViewElementDescriptor getSuperClassDesc() { return mSuperClassDesc; @@ -149,6 +163,8 @@ public class ViewElementDescriptor extends ElementDescriptor { /** * Sets the {@link ViewElementDescriptor} of the super-class of this View descriptor * that matches the java View hierarchy. Can be null. + * + * @param superClassDesc the descriptor for the super class, or null */ public void setSuperClass(ViewElementDescriptor superClassDesc) { mSuperClassDesc = superClassDesc; @@ -183,6 +199,35 @@ public class ViewElementDescriptor extends ElementDescriptor { } /** + * Returns the list of attribute sources for the attributes provided by this + * descriptor. An attribute source is the fully qualified class name of the + * defining class for some of the properties. The specific attribute source + * of a given {@link AttributeInfo} can be found by calling + * {@link AttributeInfo#getDefinedBy()}. + * <p> + * The attribute sources are ordered from class to super class. + * <p> + * The list may <b>not</b> be modified by clients. + * + * @return a non null list of attribute sources for this view + */ + public List<String> getAttributeSources() { + return mAttributeSources != null ? mAttributeSources : Collections.<String>emptyList(); + } + + /** + * Sets the attribute sources for this view. See {@link #getAttributes()} + * for details. + * + * @param attributeSources a non null list of attribute sources for this + * view descriptor + * @see #getAttributeSources() + */ + public void setAttributeSources(List<String> attributeSources) { + mAttributeSources = attributeSources; + } + + /** * Returns true if views with the given fully qualified class name need to include * their package in the layout XML tag * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index dfc30fe..7a2f7d5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -469,6 +469,7 @@ class DynamicContextMenu { Set<String> availableIds = computeApplicableActionIds(allActions); List<RuleAction> firstSelectedActions = allActions.get(mNodes.get(0)); + int count = 0; for (RuleAction firstAction : firstSelectedActions) { if (!availableIds.contains(firstAction.getId()) && !(firstAction instanceof RuleAction.Separator)) { @@ -477,6 +478,11 @@ class DynamicContextMenu { } createContributionItem(firstAction, mNodes).fill(menu, -1); + count++; + } + + if (count == 0) { + addDisabledMessageItem("<Empty>"); } } } @@ -546,7 +552,8 @@ class DynamicContextMenu { } String title = titles.get(i); - IAction a = new Action(title, IAction.AS_PUSH_BUTTON) { + IAction a = new Action(title, + current != null ? IAction.AS_CHECK_BOX : IAction.AS_PUSH_BUTTON) { @Override public void runWithEvent(Event event) { run(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java index d213646..dd24322 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java @@ -88,6 +88,7 @@ import org.eclipse.ui.dialogs.SelectionDialog; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -168,6 +169,10 @@ class ClientRulesEngine implements IClientRulesEngine { public Margins getInsets() { return mRulesEngine.getEditor().getCanvasControl().getInsets(fqcn); } + + public List<String> getTopAttributes() { + return ViewMetadataRepository.get().getTopAttributes(fqcn); + } }; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index b27954e..f29283e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -43,6 +43,7 @@ import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -387,6 +388,15 @@ public class NodeProxy implements INode { return infos; } + public List<String> getAttributeSources() { + ElementDescriptor descriptor = mNode.getDescriptor(); + if (descriptor instanceof ViewElementDescriptor) { + return ((ViewElementDescriptor) descriptor).getAttributeSources(); + } else { + return Collections.emptyList(); + } + } + public IAttribute[] getLiveAttributes() { UiElementNode uiNode = mNode; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java index 61750f9..5b4b734 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java @@ -268,10 +268,11 @@ public class ViewMetadataRepository { } String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$ + String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$ String resize = child.getAttribute("resize"); //$NON-NLS-1$ ViewData view = new ViewData(fqcn, displayName, fillPreference, skip.length() == 0 ? false : Boolean.valueOf(skip), - renderMode, relatedTo, resize); + renderMode, relatedTo, resize, topAttrs); String init = child.getAttribute("init"); //$NON-NLS-1$ String icon = child.getAttribute("icon"); //$NON-NLS-1$ @@ -384,7 +385,8 @@ public class ViewMetadataRepository { } if (remaining.size() > 0) { - List<ViewElementDescriptor> otherItems = new ArrayList<ViewElementDescriptor>(remaining); + List<ViewElementDescriptor> otherItems = + new ArrayList<ViewElementDescriptor>(remaining); // Always sorted, we don't have a natural order for these unknowns Collections.sort(otherItems); if (createCategories) { @@ -475,11 +477,13 @@ public class ViewMetadataRepository { private String mIconName; /** The resize preference of this view */ private String mResize; + /** The most commonly set attributes of this view */ + private String mTopAttrs; /** Constructs a new view data for the given class */ private ViewData(String fqcn, String displayName, FillPreference fillPreference, boolean skip, RenderMode renderMode, - String relatedTo, String resize) { + String relatedTo, String resize, String topAttrs) { super(); mFqcn = fqcn; mDisplayName = displayName; @@ -488,6 +492,7 @@ public class ViewMetadataRepository { mRenderMode = renderMode; mRelatedTo = relatedTo; mResize = resize; + mTopAttrs = topAttrs; } /** Returns the {@link FillPreference} for views of this type */ @@ -548,6 +553,22 @@ public class ViewMetadataRepository { } } + public List<String> getTopAttributes() { + // "id" is a top attribute for all views, so it is not included in the XML, we just + // add it in dynamically here + if (mTopAttrs == null || mTopAttrs.length() == 0) { + return Collections.singletonList(ATTR_ID); + } else { + String[] split = mTopAttrs.split(","); //$NON-NLS-1$ + List<String> topAttributes = new ArrayList<String>(split.length + 1); + topAttributes.add(ATTR_ID); + for (int i = 0, n = split.length; i < n; i++) { + topAttributes.add(split[i]); + } + return Collections.<String>unmodifiableList(topAttributes); + } + } + void addVariation(ViewData variation) { if (mVariations == null) { mVariations = new ArrayList<ViewData>(4); @@ -661,6 +682,23 @@ public class ViewMetadataRepository { } /** + * Returns a list of the top (most commonly set) attributes of the given + * view. + * + * @param fqcn the fully qualified class name + * @return a list, never null but possibly empty, of popular attribute names + * (not including a namespace prefix) + */ + public List<String> getTopAttributes(String fqcn) { + ViewData view = getClassToView().get(fqcn); + if (view != null) { + return view.getTopAttributes(); + } + + return Collections.singletonList(ATTR_ID); + } + + /** * Returns a set of fully qualified names for views that are closely related to the * given view * @@ -692,10 +730,17 @@ public class ViewMetadataRepository { */ SKIP; + /** + * Returns the {@link RenderMode} for the given render XML attribute + * value + * + * @param render the attribute value in the metadata XML file + * @return a corresponding {@link RenderMode}, never null + */ public static RenderMode get(String render) { - if ("alone".equals(render)) { + if ("alone".equals(render)) { //$NON-NLS-1$ return ALONE; - } else if ("skip".equals(render)) { + } else if ("skip".equals(render)) { //$NON-NLS-1$ return SKIP; } else { return NORMAL; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml index 511d775..5a0a887 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml @@ -26,6 +26,7 @@ render (alone|skip|normal) "normal" fill (none|both|width|height|opposite|width_in_vertical|height_in_horizontal) "none" resize (full|none|horizontal|vertical|scaled) "full" + topAttrs CDATA #IMPLIED > ]> <metadata> @@ -33,6 +34,7 @@ name="Form Widgets"> <view class="android.widget.TextView" + topAttrs="text,textAppearance,textColor,textSize" name="TextView" init="" relatedTo="EditText,AutoCompleteTextView,MultiAutoCompleteTextView"> @@ -48,25 +50,32 @@ </view> <view class="android.widget.Button" + topAttrs="text,style" relatedTo="ImageButton" /> <view class="android.widget.ToggleButton" + topAttrs="textOff,textOn,style,background" relatedTo="CheckBox" /> <view class="android.widget.CheckBox" + topAttrs="text" relatedTo="RadioButton,ToggleButton,CheckedTextView" /> <view class="android.widget.RadioButton" + topAttrs="text,style" relatedTo="CheckBox,ToggleButton" /> <view class="android.widget.CheckedTextView" + topAttrs="gravity,paddingLeft,paddingRight,checkMark,textAppearance" relatedTo="TextView,CheckBox" /> <view class="android.widget.Spinner" + topAttrs="prompt,entries,style" relatedTo="EditText" fill="width_in_vertical" /> <view class="android.widget.ProgressBar" + topAttrs="style,visibility,indeterminate,max" relatedTo="SeekBar" name="ProgressBar (Large)" init="style=?android:attr/progressBarStyleLarge" @@ -86,22 +95,27 @@ </view> <view class="android.widget.SeekBar" + topAttrs="paddingLeft,paddingRight,progressDrawable,thumb" relatedTo="ProgressBar" resize="horizontal" fill="width_in_vertical" /> <view class="android.widget.QuickContactBadge" + topAttrs="src,style,gravity" resize="scaled" /> <view - class="android.widget.RadioGroup" /> + class="android.widget.RadioGroup" + topAttrs="orientation,paddingBottom,paddingTop,style" /> <view class="android.widget.RatingBar" + topAttrs="numStars,stepSize,style,isIndicator" resize="horizontal" /> </category> <category name="Text Fields"> <view class="android.widget.EditText" + topAttrs="hint,inputType,singleLine" name="Plain Text" init="" resize="full" @@ -148,9 +162,11 @@ </view> <view class="android.widget.AutoCompleteTextView" + topAttrs="singleLine,autoText" fill="width_in_vertical" /> <view class="android.widget.MultiAutoCompleteTextView" + topAttrs="background,hint,imeOptions,inputType,style,textColor" fill="width_in_vertical" /> </category> <category @@ -161,6 +177,7 @@ render="skip" /> <view class="android.widget.LinearLayout" + topAttrs="orientation,gravity" name="LinearLayout (Vertical)" init="android:orientation=vertical" icon="VerticalLinearLayout" @@ -171,27 +188,33 @@ </view> <view class="android.widget.RelativeLayout" + topAttrs="background,orientation,paddingLeft" fill="opposite" render="skip" /> <view class="android.widget.FrameLayout" + topAttrs="background" fill="opposite" render="skip" /> <view class="include" + topAttrs="layout" name="Include Other Layout" render="skip" /> <view class="fragment" + topAttrs="class,name" name="Fragment" fill="opposite" render="skip" /> <view class="android.widget.TableLayout" + topAttrs="stretchColumns,shrinkColumns,orientation" fill="opposite" render="skip" /> <view class="android.widget.TableRow" + topAttrs="paddingTop,focusable,gravity,visibility" fill="opposite" resize="vertical" render="skip" /> @@ -204,39 +227,49 @@ name="Composite"> <view class="android.widget.ListView" + topAttrs="drawSelectorOnTop,cacheColorHint,divider,background" relatedTo="ExpandableListView" fill="width_in_vertical" /> <view class="android.widget.ExpandableListView" + topAttrs="drawSelectorOnTop,cacheColorHint,indicatorLeft,indicatorRight,scrollbars,textSize" relatedTo="ListView" fill="width_in_vertical" /> <view class="android.widget.GridView" + topAttrs="numColumns,verticalSpacing,horizontalSpacing" fill="opposite" render="skip" /> <view class="android.widget.ScrollView" + topAttrs="fillViewport,orientation,scrollbars" relatedTo="HorizontalScrollView" fill="opposite" render="skip" /> <view class="android.widget.HorizontalScrollView" + topAttrs="scrollbars,fadingEdgeLength,fadingEdge" relatedTo="ScrollView" render="skip" /> <view class="android.widget.SearchView" + topAttrs="iconifiedByDefault,queryHint,maxWidth,minWidth,visibility" render="skip" /> <view - class="android.widget.SlidingDrawer" /> + class="android.widget.SlidingDrawer" + topAttrs="allowSingleTap,bottomOffset,content,handle,topOffset,visibility" /> <view class="android.widget.TabHost" + topAttrs="paddingTop,background,duplicateParentState,visibility" fill="width_in_vertical" render="alone" /> <view class="android.widget.TabWidget" + topAttrs="background,paddingLeft,tabStripEnabled,gravity" render="alone" /> <view class="android.webkit.WebView" + topAttrs="background,visibility,textAppearance" fill="opposite" render="skip" /> </category> @@ -244,14 +277,17 @@ name="Images & Media"> <view class="android.widget.ImageView" + topAttrs="src,scaleType" resize="scaled" relatedTo="ImageButton,VideoView" /> <view class="android.widget.ImageButton" + topAttrs="src,background,style" resize="scaled" relatedTo="Button,ImageView" /> <view class="android.widget.Gallery" + topAttrs="gravity,spacing,background" fill="width_in_vertical" render="skip" /> <view @@ -267,6 +303,7 @@ name="Time & Date"> <view class="android.widget.TimePicker" + topAttrs="visibility" relatedTo="DatePicker,CalendarView" render="alone" /> <view @@ -275,13 +312,16 @@ render="alone" /> <view class="android.widget.CalendarView" + topAttrs="focusable,focusableInTouchMode,visibility" fill="both" relatedTo="TimePicker,DatePicker" /> <view class="android.widget.Chronometer" + topAttrs="textSize,gravity,visibility" render="skip" /> <view class="android.widget.AnalogClock" + topAttrs="dial,hand_hour,hand_minute" relatedTo="DigitalClock" /> <view class="android.widget.DigitalClock" @@ -291,14 +331,17 @@ name="Transitions"> <view class="android.widget.ImageSwitcher" + topAttrs="inAnimation,outAnimation,cropToPadding,padding,scaleType" relatedTo="ViewFlipper,ViewSwitcher,TextSwitcher" render="skip" /> <view class="android.widget.AdapterViewFlipper" + topAttrs="autoStart,flipInterval,inAnimation,outAnimation" fill="opposite" render="skip" /> <view class="android.widget.StackView" + topAttrs="loopViews,gravity" fill="opposite" render="skip" /> <view @@ -308,15 +351,18 @@ render="skip" /> <view class="android.widget.ViewAnimator" + topAttrs="inAnimation,outAnimation" fill="opposite" render="skip" /> <view class="android.widget.ViewFlipper" + topAttrs="flipInterval,inAnimation,outAnimation,addStatesFromChildren,measureAllChildren" relatedTo="ViewSwitcher,ImageSwitcher,TextSwitcher" fill="opposite" render="skip" /> <view class="android.widget.ViewSwitcher" + topAttrs="inAnimation,outAnimation" relatedTo="ViewFlipper,ImageSwitcher,TextSwitcher" fill="opposite" render="skip" /> @@ -328,12 +374,15 @@ render="skip" /> <view class="android.view.View" + topAttrs="background,visibility,style" render="skip" /> <view class="android.view.ViewStub" + topAttrs="layout,inflatedId,visibility" render="skip" /> <view class="android.gesture.GestureOverlayView" + topAttrs="gestureStrokeType,uncertainGestureColor,eventsInterceptionEnabled,gestureColor,orientation" render="skip" /> <view class="android.view.TextureView" @@ -343,17 +392,21 @@ render="skip" /> <view class="android.widget.NumberPicker" + topAttrs="focusable,focusableInTouchMode" relatedTo="TimePicker,DatePicker" render="alone" /> <view class="android.widget.ZoomButton" + topAttrs="background" relatedTo="Button,ZoomControls" /> <view class="android.widget.ZoomControls" + topAttrs="style,background,gravity" relatedTo="ZoomButton" resize="none" /> <view class="merge" + topAttrs="orientation,gravity,style" skip="true" render="skip" /> <view @@ -362,9 +415,11 @@ render="skip" /> <view class="android.widget.TwoLineListItem" + topAttrs="mode,paddingBottom,paddingTop,minHeight,paddingLeft" render="skip" /> <view class="android.widget.AbsoluteLayout" + topAttrs="background,orientation,paddingBottom,paddingLeft,paddingRight,paddingTop" name="AbsoluteLayout (Deprecated)" fill="opposite" render="skip" /> |