diff options
5 files changed, 187 insertions, 34 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java index 7c40097..bd1ac81 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java @@ -20,28 +20,45 @@ 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.VALUE_FILL_PARENT; import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.ATTR_LAYOUT; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_FRAGMENT; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.layoutlib.api.IXmlPullParser; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata; import com.android.sdklib.SdkConstants; import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; /** - * Modified {@link KXmlParser} that adds the methods of {@link ILayoutPullParser}. + * Modified {@link KXmlParser} that adds the methods of {@link ILayoutPullParser}, and + * performs other layout-specific parser behavior like translating fragment tags into + * include tags. * <p/> * It will return a given parser when queried for one through - * {@link IXmlPullParser#getParser(String)} for a given name. + * {@link ILayoutPullParser#getParser(String)} for a given name. * */ public class ContextPullParser extends KXmlParser implements ILayoutPullParser { - + private static final String COMMENT_PREFIX = "<!--"; //$NON-NLS-1$ + private static final String COMMENT_SUFFIX = "-->"; //$NON-NLS-1$ + /** The callback to request parsers from */ private final IProjectCallback mProjectCallback; + /** The {@link File} for the layout currently being parsed */ + private File mFile; + /** The layout to be shown for the current {@code <fragment>} tag. Usually null. */ + private String mFragmentLayout = null; - public ContextPullParser(IProjectCallback projectCallback) { + public ContextPullParser(IProjectCallback projectCallback, File file) { super(); mProjectCallback = projectCallback; + mFile = file; } // --- Layout lib API methods @@ -62,7 +79,28 @@ public class ContextPullParser extends KXmlParser implements ILayoutPullParser { // --- KXMLParser override @Override + public String getName() { + String name = super.getName(); + + // At designtime, replace fragments with includes. + if (name.equals(VIEW_FRAGMENT)) { + mFragmentLayout = findFragmentLayout(); + if (mFragmentLayout != null) { + return VIEW_INCLUDE; + } + } else { + mFragmentLayout = null; + } + + return name; + } + + @Override public String getAttributeValue(String namespace, String localName) { + if (localName.equals(ATTR_LAYOUT) && mFragmentLayout != null) { + return mFragmentLayout; + } + String value = super.getAttributeValue(namespace, localName); // on the fly convert match_parent to fill_parent for compatibility with older @@ -76,4 +114,58 @@ public class ContextPullParser extends KXmlParser implements ILayoutPullParser { return value; } + + /** + * This method determines whether the {@code <fragment>} tag in the current parsing + * context has been configured with a layout to render at designtime. If so, + * it returns the resource name of the layout, and if not, returns null. + */ + private String findFragmentLayout() { + try { + if (!isEmptyElementTag()) { + // We need to look inside the <fragment> tag to see + // if it contains a comment which indicates a fragment + // to be rendered. + String file = AdtPlugin.readFile(mFile); + + int line = getLineNumber() - 1; + int column = getColumnNumber() - 1; + int offset = 0; + int currentLine = 0; + int length = file.length(); + while (currentLine < line && offset < length) { + int next = file.indexOf('\n', offset); + if (next == -1) { + break; + } + + currentLine++; + offset = next + 1; + } + if (currentLine == line) { + offset += column; + if (offset < length) { + offset = file.indexOf('<', offset); + if (offset != -1 && file.startsWith(COMMENT_PREFIX, offset)) { + // The fragment tag contains a comment + int end = file.indexOf(COMMENT_SUFFIX, offset); + if (end != -1) { + String commentText = file.substring( + offset + COMMENT_PREFIX.length(), end); + String l = LayoutMetadata.getProperty(KEY_FRAGMENT_LAYOUT, + commentText); + if (l != null) { + return l; + } + } + } + } + } + } + } catch (XmlPullParserException e) { + AdtPlugin.log(e, null); + } + + return null; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java index c3698fd..c71005c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java @@ -34,6 +34,7 @@ import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.legacy.LegacyCallback; +import com.android.ide.common.resources.ResourceResolver; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata; @@ -47,7 +48,12 @@ import com.android.sdklib.xml.ManifestData; import com.android.util.Pair; import org.eclipse.core.resources.IProject; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -58,7 +64,7 @@ import java.util.TreeSet; /** * Loader for Android Project class in order to use them in the layout editor. * <p/>This implements {@link IProjectCallback} for the old and new API through - * {@link ILegacyCallback} + * {@link LegacyCallback} */ public final class ProjectCallback extends LegacyCallback { private final HashMap<String, Class<?>> mLoadedClasses = new HashMap<String, Class<?>>(); @@ -75,7 +81,7 @@ public final class ProjectCallback extends LegacyCallback { private String mLayoutName; private ILayoutPullParser mLayoutEmbeddedParser; - + private ResourceResolver mResourceResolver; /** * Creates a new {@link ProjectCallback} to be used with the layout lib. @@ -396,15 +402,51 @@ public final class ProjectCallback extends LegacyCallback { } public ILayoutPullParser getParser(String layoutName) { - if (layoutName.equals(mLayoutName)) { - return mLayoutEmbeddedParser; + // Try to compute the ResourceValue for this layout since layoutlib + // must be an older version which doesn't pass the value: + if (mResourceResolver != null) { + ResourceValue value = mResourceResolver.getProjectResource(ResourceType.LAYOUT, + layoutName); + if (value != null) { + return getParser(value); + } } - return null; + return getParser(layoutName, null); } public ILayoutPullParser getParser(ResourceValue layoutResource) { - return getParser(layoutResource.getName()); + return getParser(layoutResource.getName(), + new File(layoutResource.getValue())); + } + + private ILayoutPullParser getParser(String layoutName, File xml) { + if (layoutName.equals(mLayoutName)) { + ILayoutPullParser parser = mLayoutEmbeddedParser; + // The parser should only be used once!! If it is included more than once, + // subsequent includes should just use a plain pull parser that is not tied + // to the XML model + mLayoutEmbeddedParser = null; + return parser; + } + + // For included layouts, create a ContextPullParser such that we get the + // layout editor behavior in included layouts as well - which for example + // replaces <fragment> tags with <include>. + if (xml != null && xml.isFile()) { + ContextPullParser parser = new ContextPullParser(this, xml); + try { + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$ + return parser; + } catch (XmlPullParserException e) { + AdtPlugin.log(e, null); + } catch (FileNotFoundException e) { + // Shouldn't happen since we check isFile() above + } + } + + return null; } public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, @@ -543,4 +585,13 @@ public final class ProjectCallback extends LegacyCallback { return binding; } + + /** + * Sets the {@link ResourceResolver} to be used when looking up resources + * + * @param resolver the resolver to use + */ + public void setResourceResolver(ResourceResolver resolver) { + mResourceResolver = resolver; + } } 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 5d0f5b6..e3d484d 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 @@ -220,16 +220,10 @@ public class GraphicalEditorPart extends EditorPart private Map<ResourceType, Map<String, ResourceValue>> mConfiguredProjectRes; private ProjectCallback mProjectCallback; private boolean mNeedsRecompute = false; - private TargetListener mTargetListener; - private ConfigListener mConfigListener; private ResourceResolver mResourceResolver; - private ReloadListener mReloadListener; - - private boolean mUseExplodeMode; - private int mMinSdkVersion; private int mTargetSdkVersion; private LayoutActionBar mActionBar; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java index d2c4985..906a2ec 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java @@ -98,22 +98,7 @@ public class LayoutMetadata { Node comment = findComment(node); if (comment != null) { String text = comment.getNodeValue(); - assert text.startsWith(COMMENT_PROLOGUE); - String valuesString = text.substring(COMMENT_PROLOGUE.length()); - String[] values = valuesString.split(","); //$NON-NLS-1$ - if (values.length == 1) { - valuesString = values[0].trim(); - if (valuesString.indexOf('\n') != -1) { - values = valuesString.split("\n"); //$NON-NLS-1$ - } - } - String target = name + '='; - for (int j = 0; j < values.length; j++) { - String value = values[j].trim(); - if (value.startsWith(target)) { - return value.substring(target.length()).trim(); - } - } + return getProperty(name, text); } return null; @@ -125,6 +110,33 @@ public class LayoutMetadata { } /** + * Returns the given property specified in the given XML comment + * + * @param name the name of the property to look up + * @param text the comment text for an XML node + * @return the value stored with the given node and name, or null + */ + public static String getProperty(String name, String text) { + assert text.startsWith(COMMENT_PROLOGUE); + String valuesString = text.substring(COMMENT_PROLOGUE.length()); + String[] values = valuesString.split(","); //$NON-NLS-1$ + if (values.length == 1) { + valuesString = values[0].trim(); + if (valuesString.indexOf('\n') != -1) { + values = valuesString.split("\n"); //$NON-NLS-1$ + } + } + String target = name + '='; + for (int j = 0; j < values.length; j++) { + String value = values[j].trim(); + if (value.startsWith(target)) { + return value.substring(target.length()).trim(); + } + } + return null; + } + + /** * Sets the given property of the given DOM node to a given value, or if null clears * the property. * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java index 36cea84..a03d038 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java @@ -320,7 +320,7 @@ public class RenderService { // as it's what IXmlPullParser.getParser(String) will receive. String queryLayoutName = mEditor.getLayoutResourceName(); mProjectCallback.setLayoutParser(queryLayoutName, modelParser); - topParser = new ContextPullParser(mProjectCallback); + topParser = new ContextPullParser(mProjectCallback, layoutFile); topParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); topParser.setInput(new FileInputStream(layoutFile), "UTF-8"); //$NON-NLS-1$ } catch (XmlPullParserException e) { @@ -376,6 +376,7 @@ public class RenderService { try { mProjectCallback.setLogger(mLogger); + mProjectCallback.setResourceResolver(mResourceResolver); return mLayoutLib.createSession(params); } catch (RuntimeException t) { // Exceptions from the bridge @@ -383,6 +384,7 @@ public class RenderService { throw t; } finally { mProjectCallback.setLogger(null); + mProjectCallback.setResourceResolver(null); } } @@ -482,6 +484,7 @@ public class RenderService { RenderSession session = null; try { mProjectCallback.setLogger(mLogger); + mProjectCallback.setResourceResolver(mResourceResolver); session = mLayoutLib.createSession(params); if (session.getResult().isSuccess()) { assert session.getRootViews().size() == 1; @@ -506,6 +509,7 @@ public class RenderService { throw t; } finally { mProjectCallback.setLogger(null); + mProjectCallback.setResourceResolver(null); if (session != null) { session.dispose(); } |