aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java661
1 files changed, 661 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
new file mode 100644
index 000000000..858156884
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -0,0 +1,661 @@
+/*
+ * 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.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_PADDING;
+import static com.android.SdkConstants.AUTO_URI;
+import static com.android.SdkConstants.UNIT_DIP;
+import static com.android.SdkConstants.UNIT_DP;
+import static com.android.SdkConstants.UNIT_IN;
+import static com.android.SdkConstants.UNIT_MM;
+import static com.android.SdkConstants.UNIT_PT;
+import static com.android.SdkConstants.UNIT_PX;
+import static com.android.SdkConstants.UNIT_SP;
+import static com.android.SdkConstants.VALUE_FILL_PARENT;
+import static com.android.SdkConstants.VALUE_MATCH_PARENT;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.ide.common.res2.ValueXmlHelper;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.FragmentMenu;
+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.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.Density;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IProject;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * {@link ILayoutPullParser} implementation on top of {@link UiElementNode}.
+ * <p/>
+ * It's designed to work on layout files, and will most likely not work on other resource files.
+ * <p/>
+ * This pull parser generates {@link ViewInfo}s which key is a {@link UiElementNode}.
+ */
+public class UiElementPullParser extends BasePullParser {
+ private final static Pattern FLOAT_PATTERN = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); //$NON-NLS-1$
+
+ private final int[] sIntOut = new int[1];
+
+ private final ArrayList<UiElementNode> mNodeStack = new ArrayList<UiElementNode>();
+ private UiElementNode mRoot;
+ private final boolean mExplodedRendering;
+ private boolean mZeroAttributeIsPadding = false;
+ private boolean mIncreaseExistingPadding = false;
+ private LayoutDescriptors mDescriptors;
+ private final Density mDensity;
+
+ /**
+ * Number of pixels to pad views with in exploded-rendering mode.
+ */
+ private static final String DEFAULT_PADDING_VALUE =
+ ExplodedRenderingHelper.PADDING_VALUE + UNIT_PX;
+
+ /**
+ * Number of pixels to pad exploded individual views with. (This is HALF the width of the
+ * rectangle since padding is repeated on both sides of the empty content.)
+ */
+ private static final String FIXED_PADDING_VALUE = "20px"; //$NON-NLS-1$
+
+ /**
+ * Set of nodes that we want to auto-pad using {@link #FIXED_PADDING_VALUE} as the padding
+ * attribute value. Can be null, which is the case when we don't want to perform any
+ * <b>individual</b> node exploding.
+ */
+ private final Set<UiElementNode> mExplodeNodes;
+
+ /**
+ * Constructs a new {@link UiElementPullParser}, a parser dedicated to the special case of
+ * parsing a layout resource files, and handling "exploded rendering" - adding padding on views
+ * to make them easier to see and operate on.
+ *
+ * @param top The {@link UiElementNode} for the root node.
+ * @param explodeRendering When true, add padding to <b>all</b> nodes in the hierarchy. This
+ * will add rather than replace padding of a node.
+ * @param explodeNodes A set of individual nodes that should be assigned a fixed amount of
+ * padding ({@link #FIXED_PADDING_VALUE}). This is intended for use with nodes that
+ * (without padding) would be invisible. This parameter can be null, in which case
+ * nodes are not individually exploded (but they may all be exploded with the
+ * explodeRendering parameter.
+ * @param density the density factor for the screen.
+ * @param project Project containing this layout.
+ */
+ public UiElementPullParser(UiElementNode top, boolean explodeRendering,
+ Set<UiElementNode> explodeNodes,
+ Density density, IProject project) {
+ super();
+ mRoot = top;
+ mExplodedRendering = explodeRendering;
+ mExplodeNodes = explodeNodes;
+ mDensity = density;
+ if (mExplodedRendering) {
+ // get the layout descriptor
+ IAndroidTarget target = Sdk.getCurrent().getTarget(project);
+ AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
+ mDescriptors = data.getLayoutDescriptors();
+ }
+ push(mRoot);
+ }
+
+ protected UiElementNode getCurrentNode() {
+ if (mNodeStack.size() > 0) {
+ return mNodeStack.get(mNodeStack.size()-1);
+ }
+
+ return null;
+ }
+
+ private Node getAttribute(int i) {
+ if (mParsingState != START_TAG) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ // get the current uiNode
+ UiElementNode uiNode = getCurrentNode();
+
+ // get its xml node
+ Node xmlNode = uiNode.getXmlNode();
+
+ if (xmlNode != null) {
+ return xmlNode.getAttributes().item(i);
+ }
+
+ return null;
+ }
+
+ private void push(UiElementNode node) {
+ mNodeStack.add(node);
+
+ mZeroAttributeIsPadding = false;
+ mIncreaseExistingPadding = false;
+
+ if (mExplodedRendering) {
+ // first get the node name
+ String xml = node.getDescriptor().getXmlLocalName();
+ ViewElementDescriptor descriptor = mDescriptors.findDescriptorByTag(xml);
+ if (descriptor != null) {
+ NamedNodeMap attributes = node.getXmlNode().getAttributes();
+ Node padding = attributes.getNamedItemNS(ANDROID_URI, ATTR_PADDING);
+ if (padding == null) {
+ // we'll return an extra padding
+ mZeroAttributeIsPadding = true;
+ } else {
+ mIncreaseExistingPadding = true;
+ }
+ }
+ }
+ }
+
+ private UiElementNode pop() {
+ return mNodeStack.remove(mNodeStack.size()-1);
+ }
+
+ // ------------- IXmlPullParser --------
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * This implementation returns the underlying DOM node of type {@link UiElementNode}.
+ * Note that the link between the GLE and the parsing code depends on this being the actual
+ * type returned, so you can't just randomly change it here.
+ * <p/>
+ * Currently used by:
+ * - private method GraphicalLayoutEditor#updateNodeWithBounds(ILayoutViewInfo).
+ * - private constructor of LayoutCanvas.CanvasViewInfo.
+ */
+ @Override
+ public Object getViewCookie() {
+ return getCurrentNode();
+ }
+
+ /**
+ * Legacy method required by {@link com.android.layoutlib.api.IXmlPullParser}
+ */
+ @Override
+ public Object getViewKey() {
+ return getViewCookie();
+ }
+
+ /**
+ * This implementation does nothing for now as all the embedded XML will use a normal KXML
+ * parser.
+ */
+ @Override
+ public ILayoutPullParser getParser(String layoutName) {
+ return null;
+ }
+
+ // ------------- XmlPullParser --------
+
+ @Override
+ public String getPositionDescription() {
+ return "XML DOM element depth:" + mNodeStack.size();
+ }
+
+ /*
+ * This does not seem to be called by the layoutlib, but we keep this (and maintain
+ * it) just in case.
+ */
+ @Override
+ public int getAttributeCount() {
+ UiElementNode node = getCurrentNode();
+
+ if (node != null) {
+ Collection<UiAttributeNode> attributes = node.getAllUiAttributes();
+ int count = attributes.size();
+
+ return count + (mZeroAttributeIsPadding ? 1 : 0);
+ }
+
+ return 0;
+ }
+
+ /*
+ * This does not seem to be called by the layoutlib, but we keep this (and maintain
+ * it) just in case.
+ */
+ @Override
+ public String getAttributeName(int i) {
+ if (mZeroAttributeIsPadding) {
+ if (i == 0) {
+ return ATTR_PADDING;
+ } else {
+ i--;
+ }
+ }
+
+ Node attribute = getAttribute(i);
+ if (attribute != null) {
+ return attribute.getLocalName();
+ }
+
+ return null;
+ }
+
+ /*
+ * This does not seem to be called by the layoutlib, but we keep this (and maintain
+ * it) just in case.
+ */
+ @Override
+ public String getAttributeNamespace(int i) {
+ if (mZeroAttributeIsPadding) {
+ if (i == 0) {
+ return ANDROID_URI;
+ } else {
+ i--;
+ }
+ }
+
+ Node attribute = getAttribute(i);
+ if (attribute != null) {
+ return attribute.getNamespaceURI();
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ /*
+ * This does not seem to be called by the layoutlib, but we keep this (and maintain
+ * it) just in case.
+ */
+ @Override
+ public String getAttributePrefix(int i) {
+ if (mZeroAttributeIsPadding) {
+ if (i == 0) {
+ // figure out the prefix associated with the android namespace.
+ Document doc = mRoot.getXmlDocument();
+ return doc.lookupPrefix(ANDROID_URI);
+ } else {
+ i--;
+ }
+ }
+
+ Node attribute = getAttribute(i);
+ if (attribute != null) {
+ return attribute.getPrefix();
+ }
+ return null;
+ }
+
+ /*
+ * This does not seem to be called by the layoutlib, but we keep this (and maintain
+ * it) just in case.
+ */
+ @Override
+ public String getAttributeValue(int i) {
+ if (mZeroAttributeIsPadding) {
+ if (i == 0) {
+ return DEFAULT_PADDING_VALUE;
+ } else {
+ i--;
+ }
+ }
+
+ Node attribute = getAttribute(i);
+ if (attribute != null) {
+ String value = attribute.getNodeValue();
+ if (mIncreaseExistingPadding && ATTR_PADDING.equals(attribute.getLocalName()) &&
+ ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ // add the padding and return the value
+ return addPaddingToValue(value);
+ }
+ return value;
+ }
+
+ return null;
+ }
+
+ /*
+ * This is the main method used by the LayoutInflater to query for attributes.
+ */
+ @Override
+ public String getAttributeValue(String namespace, String localName) {
+ if (mExplodeNodes != null && ATTR_PADDING.equals(localName) &&
+ ANDROID_URI.equals(namespace)) {
+ UiElementNode node = getCurrentNode();
+ if (node != null && mExplodeNodes.contains(node)) {
+ return FIXED_PADDING_VALUE;
+ }
+ }
+
+ if (mZeroAttributeIsPadding && ATTR_PADDING.equals(localName) &&
+ ANDROID_URI.equals(namespace)) {
+ return DEFAULT_PADDING_VALUE;
+ }
+
+ // get the current uiNode
+ UiElementNode uiNode = getCurrentNode();
+
+ // get its xml node
+ Node xmlNode = uiNode.getXmlNode();
+
+ if (xmlNode != null) {
+ if (ATTR_LAYOUT.equals(localName) && VIEW_FRAGMENT.equals(xmlNode.getNodeName())) {
+ String layout = FragmentMenu.getFragmentLayout(xmlNode);
+ if (layout != null) {
+ return layout;
+ }
+ }
+
+ Node attribute = xmlNode.getAttributes().getNamedItemNS(namespace, localName);
+
+ // Auto-convert http://schemas.android.com/apk/res-auto resources. The lookup
+ // will be for the current application's resource package, e.g.
+ // http://schemas.android.com/apk/res/foo.bar, but the XML document will
+ // be using http://schemas.android.com/apk/res-auto in library projects:
+ if (attribute == null && namespace != null && !namespace.equals(ANDROID_URI)) {
+ attribute = xmlNode.getAttributes().getNamedItemNS(AUTO_URI, localName);
+ }
+
+ if (attribute != null) {
+ String value = attribute.getNodeValue();
+ if (mIncreaseExistingPadding && ATTR_PADDING.equals(localName) &&
+ ANDROID_URI.equals(namespace)) {
+ // add the padding and return the value
+ return addPaddingToValue(value);
+ }
+
+ // on the fly convert match_parent to fill_parent for compatibility with older
+ // platforms.
+ if (VALUE_MATCH_PARENT.equals(value) &&
+ (ATTR_LAYOUT_WIDTH.equals(localName) ||
+ ATTR_LAYOUT_HEIGHT.equals(localName)) &&
+ ANDROID_URI.equals(namespace)) {
+ return VALUE_FILL_PARENT;
+ }
+
+ // Handle unicode escapes etc
+ value = ValueXmlHelper.unescapeResourceString(value, false, false);
+
+ return value;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getDepth() {
+ return mNodeStack.size();
+ }
+
+ @Override
+ public String getName() {
+ if (mParsingState == START_TAG || mParsingState == END_TAG) {
+ String name = getCurrentNode().getDescriptor().getXmlLocalName();
+
+ if (name.equals(VIEW_FRAGMENT)) {
+ // Temporarily translate <fragment> to <include> (and in getAttribute
+ // we will also provide a layout-attribute for the corresponding
+ // fragment name attribute)
+ String layout = FragmentMenu.getFragmentLayout(getCurrentNode().getXmlNode());
+ if (layout != null) {
+ return VIEW_INCLUDE;
+ }
+ }
+
+ return name;
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getNamespace() {
+ if (mParsingState == START_TAG || mParsingState == END_TAG) {
+ return getCurrentNode().getDescriptor().getNamespace();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getPrefix() {
+ if (mParsingState == START_TAG || mParsingState == END_TAG) {
+ Document doc = mRoot.getXmlDocument();
+ return doc.lookupPrefix(getCurrentNode().getDescriptor().getNamespace());
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ if (mParsingState == START_TAG) {
+ return getCurrentNode().getUiChildren().size() == 0;
+ }
+
+ throw new XmlPullParserException("Call to isEmptyElementTag while not in START_TAG",
+ this, null);
+ }
+
+ @Override
+ public void onNextFromStartDocument() {
+ onNextFromStartTag();
+ }
+
+ @Override
+ public void onNextFromStartTag() {
+ // get the current node, and look for text or children (children first)
+ UiElementNode node = getCurrentNode();
+ List<UiElementNode> children = node.getUiChildren();
+ if (children.size() > 0) {
+ // move to the new child, and don't change the state.
+ push(children.get(0));
+
+ // in case the current state is CURRENT_DOC, we set the proper state.
+ mParsingState = START_TAG;
+ } else {
+ if (mParsingState == START_DOCUMENT) {
+ // this handles the case where there's no node.
+ mParsingState = END_DOCUMENT;
+ } else {
+ mParsingState = END_TAG;
+ }
+ }
+ }
+
+ @Override
+ public void onNextFromEndTag() {
+ // look for a sibling. if no sibling, go back to the parent
+ UiElementNode node = getCurrentNode();
+ node = node.getUiNextSibling();
+ if (node != null) {
+ // to go to the sibling, we need to remove the current node,
+ pop();
+ // and add its sibling.
+ push(node);
+ mParsingState = START_TAG;
+ } else {
+ // move back to the parent
+ pop();
+
+ // we have only one element left (mRoot), then we're done with the document.
+ if (mNodeStack.size() == 1) {
+ mParsingState = END_DOCUMENT;
+ } else {
+ mParsingState = END_TAG;
+ }
+ }
+ }
+
+ // ------- TypedValue stuff
+ // This is adapted from com.android.layoutlib.bridge.ResourceHelper
+ // (but modified to directly take the parsed value and convert it into pixel instead of
+ // storing it into a TypedValue)
+ // this was originally taken from platform/frameworks/base/libs/utils/ResourceTypes.cpp
+
+ private static final class DimensionEntry {
+ String name;
+ int type;
+
+ DimensionEntry(String name, int unit) {
+ this.name = name;
+ this.type = unit;
+ }
+ }
+
+ /** {@link DimensionEntry} complex unit: Value is raw pixels. */
+ private static final int COMPLEX_UNIT_PX = 0;
+ /** {@link DimensionEntry} complex unit: Value is Device Independent
+ * Pixels. */
+ private static final int COMPLEX_UNIT_DIP = 1;
+ /** {@link DimensionEntry} complex unit: Value is a scaled pixel. */
+ private static final int COMPLEX_UNIT_SP = 2;
+ /** {@link DimensionEntry} complex unit: Value is in points. */
+ private static final int COMPLEX_UNIT_PT = 3;
+ /** {@link DimensionEntry} complex unit: Value is in inches. */
+ private static final int COMPLEX_UNIT_IN = 4;
+ /** {@link DimensionEntry} complex unit: Value is in millimeters. */
+ private static final int COMPLEX_UNIT_MM = 5;
+
+ private final static DimensionEntry[] sDimensions = new DimensionEntry[] {
+ new DimensionEntry(UNIT_PX, COMPLEX_UNIT_PX),
+ new DimensionEntry(UNIT_DIP, COMPLEX_UNIT_DIP),
+ new DimensionEntry(UNIT_DP, COMPLEX_UNIT_DIP),
+ new DimensionEntry(UNIT_SP, COMPLEX_UNIT_SP),
+ new DimensionEntry(UNIT_PT, COMPLEX_UNIT_PT),
+ new DimensionEntry(UNIT_IN, COMPLEX_UNIT_IN),
+ new DimensionEntry(UNIT_MM, COMPLEX_UNIT_MM),
+ };
+
+ /**
+ * Adds padding to an existing dimension.
+ * <p/>This will resolve the attribute value (which can be px, dip, dp, sp, pt, in, mm) to
+ * a pixel value, add the padding value ({@link ExplodedRenderingHelper#PADDING_VALUE}),
+ * and then return a string with the new value as a px string ("42px");
+ * If the conversion fails, only the special padding is returned.
+ */
+ private String addPaddingToValue(String s) {
+ int padding = ExplodedRenderingHelper.PADDING_VALUE;
+ if (stringToPixel(s)) {
+ padding += sIntOut[0];
+ }
+
+ return padding + UNIT_PX;
+ }
+
+ /**
+ * Convert the string into a pixel value, and puts it in {@link #sIntOut}
+ * @param s the dimension value from an XML attribute
+ * @return true if success.
+ */
+ private boolean stringToPixel(String s) {
+ // remove the space before and after
+ s = s.trim();
+ int len = s.length();
+
+ if (len <= 0) {
+ return false;
+ }
+
+ // check that there's no non ASCII characters.
+ char[] buf = s.toCharArray();
+ for (int i = 0 ; i < len ; i++) {
+ if (buf[i] > 255) {
+ return false;
+ }
+ }
+
+ // check the first character
+ if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') {
+ return false;
+ }
+
+ // now look for the string that is after the float...
+ Matcher m = FLOAT_PATTERN.matcher(s);
+ if (m.matches()) {
+ String f_str = m.group(1);
+ String end = m.group(2);
+
+ float f;
+ try {
+ f = Float.parseFloat(f_str);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen with the regexp above.
+ return false;
+ }
+
+ if (end.length() > 0 && end.charAt(0) != ' ') {
+ // We only support dimension-type values, so try to parse the unit for dimension
+ DimensionEntry dimension = parseDimension(end);
+ if (dimension != null) {
+ // convert the value into pixel based on the dimention type
+ // This is similar to TypedValue.applyDimension()
+ switch (dimension.type) {
+ case COMPLEX_UNIT_PX:
+ // do nothing, value is already in px
+ break;
+ case COMPLEX_UNIT_DIP:
+ case COMPLEX_UNIT_SP: // intended fall-through since we don't
+ // adjust for font size
+ f *= (float)mDensity.getDpiValue() / Density.DEFAULT_DENSITY;
+ break;
+ case COMPLEX_UNIT_PT:
+ f *= mDensity.getDpiValue() * (1.0f / 72);
+ break;
+ case COMPLEX_UNIT_IN:
+ f *= mDensity.getDpiValue();
+ break;
+ case COMPLEX_UNIT_MM:
+ f *= mDensity.getDpiValue() * (1.0f / 25.4f);
+ break;
+ }
+
+ // store result (converted to int)
+ sIntOut[0] = (int) (f + 0.5);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static DimensionEntry parseDimension(String str) {
+ str = str.trim();
+
+ for (DimensionEntry d : sDimensions) {
+ if (d.name.equals(str)) {
+ return d;
+ }
+ }
+
+ return null;
+ }
+}