diff options
author | Tor Norbye <tnorbye@google.com> | 2012-01-23 09:30:34 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-01-24 17:41:09 -0800 |
commit | 1ca78c7216b84d4f606c74a34d71409591c3af4b (patch) | |
tree | 49521c54c4e386d6993643d11c696ee8b63956da | |
parent | edca7488ef5ebb2bc10d551a3367c683fa93aef8 (diff) | |
download | sdk-1ca78c7216b84d4f606c74a34d71409591c3af4b.zip sdk-1ca78c7216b84d4f606c74a34d71409591c3af4b.tar.gz sdk-1ca78c7216b84d4f606c74a34d71409591c3af4b.tar.bz2 |
Add render logger hyperlinks to define layout_width and height
If a layout requires the layout_width or layout_height to be defined
(which all layouts except GridLayout do), and an XML layout does not
define it, then the view will throw an exception during render, which
states that the attribute must be defined, but does not state which
element is missing it.
This changeset adds recognition code in the RenderLogger which detects
when this render failure happens, and when listing the render errors
it performs a check in the XML to identify the offending element(s)
and lists them. It also adds hyperlinks to directly set the value to
either wrap_content or fill_parent (or match_parent if the API level
>= 8).
This addresses 24624: ADT GLE tells me to supply layout_width/height
attributes... but to what?
Change-Id: I967a72e61dc7d494c213111a86bd5865943e07fe
5 files changed, 180 insertions, 43 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java index cae0259..b20d190 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java @@ -383,7 +383,9 @@ public class LayoutEditorDelegate extends CommonXmlDelegate @Override public void done(IJobChangeEvent event) { LayoutActionBar bar = getGraphicalEditor().getLayoutActionBar(); - bar.updateErrorIndicator(); + if (!bar.isDisposed()) { + bar.updateErrorIndicator(); + } } }); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java index 8725fa4..2da7678 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java @@ -86,6 +86,30 @@ public class DomUtilities { } /** + * Returns all elements below the given node (which can be a document, + * element, etc). This will include the node itself, if it is an element. + * + * @param node the node to search from + * @return all elements in the subtree formed by the node parameter + */ + public static List<Element> getAllElements(Node node) { + List<Element> elements = new ArrayList<Element>(64); + addElements(node, elements); + return elements; + } + + private static void addElements(Node node, List<Element> elements) { + if (node instanceof Element) { + elements.add((Element) node); + } + + NodeList childNodes = node.getChildNodes(); + for (int i = 0, n = childNodes.getLength(); i < n; i++) { + addElements(childNodes.item(i), elements); + } + } + + /** * Returns the depth of the given node (with the document node having depth 0, * and the document element having depth 1) * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 8504bf9..236d5fd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -17,13 +17,22 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_STRING_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +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_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW; import static com.android.ide.common.layout.LayoutConstants.STRING_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage; import static com.android.sdklib.SdkConstants.FD_GEN_SOURCES; import com.android.ide.common.api.Rect; +import com.android.ide.common.layout.BaseLayoutRule; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.StaticRenderSession; import com.android.ide.common.rendering.api.Capability; @@ -164,7 +173,7 @@ import java.util.Set; * @since GLE2 */ public class GraphicalEditorPart extends EditorPart - implements IPageImageProvider, ISelectionListener, INullSelectionListener { + implements IPageImageProvider, INullSelectionListener { /* * Useful notes: @@ -1628,15 +1637,15 @@ public class GraphicalEditorPart extends EditorPart addTypoSuggestions(clazz, getAndroidViewClassNames(project), false); addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path", clazz, null); + ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path", clazz); addText(mErrorLabel, ", "); addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML", clazz, null); + ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML", clazz); if (clazz.indexOf('.') != -1) { // Add "Create Class" link, but only for custom views addText(mErrorLabel, ", "); addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class", clazz, null); + ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class", clazz); } addText(mErrorLabel, ")\n"); } @@ -1651,10 +1660,10 @@ public class GraphicalEditorPart extends EditorPart addText(mErrorLabel, "- "); addText(mErrorLabel, " ("); addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz, null); + ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz); addText(mErrorLabel, ", "); addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log", clazz, null); + ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log", clazz); addText(mErrorLabel, ")\n"); if (!(clazz.startsWith("android.") || //$NON-NLS-1$ @@ -1705,9 +1714,8 @@ public class GraphicalEditorPart extends EditorPart // Only show full package name if class name // is the same labelClass), - actual, - viewNeedsPackage(suggested) ? suggested : suggestedBase - ); + actual, + viewNeedsPackage(suggested) ? suggested : suggestedBase); addText(mErrorLabel, ", "); } } @@ -1803,6 +1811,21 @@ public class GraphicalEditorPart extends EditorPart addBoldText(mErrorLabel, message); } + if (logger.seenTag(RenderLogger.TAG_MISSING_DIMENSION)) { + List<UiElementNode> elements = UiDocumentNode.getAllElements(getModel()); + for (UiElementNode element : elements) { + String width = element.getAttributeValue(ATTR_LAYOUT_WIDTH); + if (width == null || width.length() == 0) { + addSetAttributeLink(element, ATTR_LAYOUT_WIDTH); + } + + String height = element.getAttributeValue(ATTR_LAYOUT_HEIGHT); + if (height == null || height.length() == 0) { + addSetAttributeLink(element, ATTR_LAYOUT_HEIGHT); + } + } + } + String problems = logger.getProblems(false /*includeFidelityWarnings*/); addText(mErrorLabel, problems); @@ -1814,7 +1837,7 @@ public class GraphicalEditorPart extends EditorPart addText(mErrorLabel, warning + ' '); addActionLink(mErrorLabel, ActionLinkStyleRange.IGNORE_FIDELITY_WARNING, - "(Ignore for this session)\n", warning, null); + "(Ignore for this session)\n", warning); } } @@ -1824,6 +1847,44 @@ public class GraphicalEditorPart extends EditorPart } } + /** Appends an action link to set the given attribute on the given value */ + private void addSetAttributeLink(UiElementNode element, String attribute) { + if (element.getXmlNode().getNodeName().equals(GRID_LAYOUT)) { + // GridLayout does not require a layout_width or layout_height to be defined + return; + } + + String fill = VALUE_FILL_PARENT; + // See whether we should offer match_parent instead of fill_parent + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(getProject()); + if (target.getVersion().getApiLevel() >= 8) { + fill = VALUE_MATCH_PARENT; + } + } + + String id = element.getAttributeValue(ATTR_ID); + if (id == null || id.length() == 0) { + id = '<' + element.getXmlNode().getNodeName() + '>'; + } else { + id = BaseLayoutRule.stripIdPrefix(id); + } + + addText(mErrorLabel, String.format("\"%1$s\" does not set the required %2$s attribute:\n", + id, attribute)); + addText(mErrorLabel, " (1) "); + addActionLink(mErrorLabel, + ActionLinkStyleRange.SET_ATTRIBUTE, + String.format("Set to \"%1$s\"", VALUE_WRAP_CONTENT), + element, attribute, VALUE_WRAP_CONTENT); + addText(mErrorLabel, "\n (2) "); + addActionLink(mErrorLabel, + ActionLinkStyleRange.SET_ATTRIBUTE, + String.format("Set to \"%1$s\"\n", fill), + element, attribute, fill); + } + /** Appends the given text as a bold string in the given text widget */ private void addBoldText(StyledText styledText, String text) { String s = styledText.getText(); @@ -1844,12 +1905,12 @@ public class GraphicalEditorPart extends EditorPart * action, corresponding to the value fields in {@link ActionLinkStyleRange}. */ private void addActionLink(StyledText styledText, int action, String label, - String data1, String data2) { + Object... data) { String s = styledText.getText(); int start = (s == null ? 0 : s.length()); styledText.append(label); - StyleRange sr = new ActionLinkStyleRange(action, data1, data2); + StyleRange sr = new ActionLinkStyleRange(action, data); sr.start = start; sr.length = label.length(); sr.fontStyle = SWT.NORMAL; @@ -1997,26 +2058,25 @@ public class GraphicalEditorPart extends EditorPart private static final int LINK_CHANGE_CLASS_TO = 6; /** Ignore the given fidelity warning */ private static final int IGNORE_FIDELITY_WARNING = 7; + /** Set an attribute on the given XML element to a given value */ + private static final int SET_ATTRIBUTE = 8; - /** Client data 1 - usually the class name */ - private final String mData1; - /** Client data 2 - such as the suggested new name */ - private final String mData2; + /** Client data: the contents depend on the specific action */ + private final Object[] mData; /** The action to be taken when the link is clicked */ private final int mAction; - private ActionLinkStyleRange(int action, String data1, String data2) { + private ActionLinkStyleRange(int action, Object... data) { super(); mAction = action; - mData1 = data1; - mData2 = data2; + mData = data; } /** Performs the click action */ public void onClick() { switch (mAction) { case LINK_CREATE_CLASS: - createNewClass(mData1); + createNewClass((String) mData[0]); break; case LINK_EDIT_XML: mEditorDelegate.getEditor().setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); @@ -2029,7 +2089,7 @@ public class GraphicalEditorPart extends EditorPart getProject(), id, null, null).open(); break; case LINK_OPEN_CLASS: - AdtPlugin.openJavaClass(getProject(), mData1); + AdtPlugin.openJavaClass(getProject(), (String) mData[0]); break; case LINK_SHOW_LOG: IWorkbench workbench = PlatformUI.getWorkbench(); @@ -2042,7 +2102,7 @@ public class GraphicalEditorPart extends EditorPart } break; case LINK_CHANGE_CLASS_TO: - // Change class reference of mData1 to mData2 + // Change class reference of mData[0] to mData[1] // TODO: run under undo lock MultiTextEdit edits = new MultiTextEdit(); ISourceViewer textViewer = @@ -2052,8 +2112,8 @@ public class GraphicalEditorPart extends EditorPart int index = 0; // Replace <old with <new and </old with </new String prefix = "<"; //$NON-NLS-1$ - String find = prefix + mData1; - String replaceWith = prefix + mData2; + String find = prefix + mData[0]; + String replaceWith = prefix + mData[1]; while (true) { index = xml.indexOf(find, index); if (index == -1) { @@ -2064,8 +2124,8 @@ public class GraphicalEditorPart extends EditorPart } index = 0; prefix = "</"; //$NON-NLS-1$ - find = prefix + mData1; - replaceWith = prefix + mData2; + find = prefix + mData[0]; + replaceWith = prefix + mData[1]; while (true) { index = xml.indexOf(find, index); if (index == -1) { @@ -2078,8 +2138,8 @@ public class GraphicalEditorPart extends EditorPart index = 0; prefix = "\""; //$NON-NLS-1$ String suffix = "\""; //$NON-NLS-1$ - find = prefix + mData1 + suffix; - replaceWith = prefix + mData2 + suffix; + find = prefix + mData[0] + suffix; + replaceWith = prefix + mData[1] + suffix; while (true) { index = xml.indexOf(find, index); if (index == -1) { @@ -2097,10 +2157,26 @@ public class GraphicalEditorPart extends EditorPart } break; case IGNORE_FIDELITY_WARNING: - RenderLogger.ignoreFidelityWarning(mData1); + RenderLogger.ignoreFidelityWarning((String) mData[0]); recomputeLayout(); break; + case SET_ATTRIBUTE: { + final UiElementNode element = (UiElementNode) mData[0]; + final String attribute = (String) mData[1]; + final String value = (String) mData[2]; + mEditorDelegate.getEditor().wrapUndoEditXmlModel( + String.format("Set \"%1$s\" to \"%2$s\"", attribute, value), + new Runnable() { + @Override + public void run() { + element.setAttributeValue(attribute, ANDROID_URI, value, true); + element.commitDirtyAttributesToXml(); + } + }); + break; + } default: + assert false : mAction; break; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java index 83bd95f..74bc4f5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java @@ -31,6 +31,8 @@ import java.util.Set; * single summary at the end */ class RenderLogger extends LayoutLog { + static final String TAG_MISSING_DIMENSION = "missing.dimension"; //$NON-NLS-1$ + private final String mName; private List<String> mFidelityWarnings; private List<String> mWarnings; @@ -143,6 +145,14 @@ class RenderLogger extends LayoutLog { public void warning(String tag, String message, Object data) { String description = describe(message); AdtPlugin.log(IStatus.WARNING, "%1$s: %2$s", mName, description); + + if (TAG_RESOURCES_FORMAT.equals(tag)) { + if (description.equals("You must supply a layout_width attribute.") //$NON-NLS-1$ + || description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$ + tag = TAG_MISSING_DIMENSION; + } + } + addWarning(tag, description); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java index beac5e1..1a85ea6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiDocumentNode.java @@ -22,6 +22,9 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.Ui import org.w3c.dom.Document; import org.w3c.dom.Node; +import java.util.ArrayList; +import java.util.List; + /** * Represents an XML document node that can be modified by the user interface in the XML editor. * <p/> @@ -29,10 +32,10 @@ import org.w3c.dom.Node; * {@link DocumentDescriptor}. */ public class UiDocumentNode extends UiElementNode { - + /** * Creates a new {@link UiDocumentNode} described by a given {@link DocumentDescriptor}. - * + * * @param documentDescriptor The {@link DocumentDescriptor} for the XML node. Cannot be null. */ public UiDocumentNode(DocumentDescriptor documentDescriptor) { @@ -43,17 +46,17 @@ public class UiDocumentNode extends UiElementNode { * Computes a short string describing the UI node suitable for tree views. * Uses the element's attribute "android:name" if present, or the "android:label" one * followed by the element's name. - * + * * @return A short string describing the UI node suitable for tree views. */ @Override public String getShortDescription() { return "Document"; //$NON-NLS-1$ } - + /** * Computes a "breadcrumb trail" description for this node. - * + * * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect * when called on the root node itself. * @return The "breadcrumb trail" description for this node. @@ -62,7 +65,7 @@ public class UiDocumentNode extends UiElementNode { public String getBreadcrumbTrailDescription(boolean include_root) { return "Document"; //$NON-NLS-1$ } - + /** * This method throws an exception when attempted to assign a parent, since XML documents * cannot have a parent. It is OK to assign null. @@ -78,13 +81,13 @@ public class UiDocumentNode extends UiElementNode { /** * Populate this element node with all values from the given XML node. - * + * * This fails if the given XML node has a different element name -- it won't change the * type of this ui node. - * + * * This method can be both used for populating values the first time and updating values * after the XML model changed. - * + * * @param xml_node The XML node to mirror * @return Returns true if the XML structure has changed (nodes added, removed or replaced) */ @@ -98,13 +101,13 @@ public class UiDocumentNode extends UiElementNode { } return structure_changed; } - + /** * This method throws an exception if there is no underlying XML document. * <p/> * XML documents cannot be created per se -- they are a by-product of the StructuredEditor * XML parser. - * + * * @return The current value of getXmlDocument(). */ @Override @@ -123,13 +126,35 @@ public class UiDocumentNode extends UiElementNode { * <p/> * XML documents cannot be deleted per se -- they are a by-product of the StructuredEditor * XML parser. - * - * @return The removed node or null if it didn't exist in the firtst place. + * + * @return The removed node or null if it didn't exist in the first place. */ @Override public Node deleteXmlNode() { // DEBUG. Change to log warning. throw new UnsupportedOperationException("Documents cannot be deleted"); //$NON-NLS-1$ } + + /** + * Returns all elements in this document. + * + * @param document the document + * @return all elements in the document + */ + public static List<UiElementNode> getAllElements(UiDocumentNode document) { + List<UiElementNode> elements = new ArrayList<UiElementNode>(64); + for (UiElementNode child : document.getUiChildren()) { + addElements(child, elements); + } + return elements; + } + + private static void addElements(UiElementNode node, List<UiElementNode> elements) { + elements.add(node); + + for (UiElementNode child : node.getUiChildren()) { + addElements(child, elements); + } + } } |