diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java | 413 |
1 files changed, 413 insertions, 0 deletions
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 new file mode 100644 index 000000000..b79e3b0a1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2011 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.gle2; + +import static com.android.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX; +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_NUM_COLUMNS; +import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; +import static com.android.SdkConstants.GRID_VIEW; +import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; +import static com.android.SdkConstants.TOOLS_URI; +import static com.android.SdkConstants.VALUE_AUTO_FIT; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.progress.WorkbenchJob; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Design-time metadata lookup for layouts, such as fragment and AdapterView bindings. + */ +public class LayoutMetadata { + /** The default layout to use for list items in expandable list views */ + public static final String DEFAULT_EXPANDABLE_LIST_ITEM = "simple_expandable_list_item_2"; //$NON-NLS-1$ + /** The default layout to use for list items in plain list views */ + public static final String DEFAULT_LIST_ITEM = "simple_list_item_2"; //$NON-NLS-1$ + /** The default layout to use for list items in spinners */ + public static final String DEFAULT_SPINNER_ITEM = "simple_spinner_item"; //$NON-NLS-1$ + + /** The string to start metadata comments with */ + private static final String COMMENT_PROLOGUE = " Preview: "; + /** The property key, included in comments, which references a list item layout */ + public static final String KEY_LV_ITEM = "listitem"; //$NON-NLS-1$ + /** The property key, included in comments, which references a list header layout */ + public static final String KEY_LV_HEADER = "listheader"; //$NON-NLS-1$ + /** The property key, included in comments, which references a list footer layout */ + public static final String KEY_LV_FOOTER = "listfooter"; //$NON-NLS-1$ + /** The property key, included in comments, which references a fragment layout to show */ + public static final String KEY_FRAGMENT_LAYOUT = "layout"; //$NON-NLS-1$ + // NOTE: If you add additional keys related to resources, make sure you update the + // ResourceRenameParticipant + + /** Utility class, do not create instances */ + private LayoutMetadata() { + } + + /** + * Returns the given property specified in the <b>current</b> element being + * processed by the given pull parser. + * + * @param parser the pull parser, which must be in the middle of processing + * the target element + * @param name the property name to look up + * @return the property value, or null if not defined + */ + @Nullable + public static String getProperty(@NonNull XmlPullParser parser, @NonNull String name) { + String value = parser.getAttributeValue(TOOLS_URI, name); + if (value != null && value.isEmpty()) { + value = null; + } + + return value; + } + + /** + * Clears the old metadata from the given node + * + * @param node the XML node to associate metadata with + * @deprecated this method clears metadata using the old comment-based style; + * should only be used for migration at this point + */ + @Deprecated + public static void clearLegacyComment(Node node) { + NodeList children = node.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.COMMENT_NODE) { + String text = child.getNodeValue(); + if (text.startsWith(COMMENT_PROLOGUE)) { + Node commentNode = child; + // Remove the comment, along with surrounding whitespace if applicable + Node previous = commentNode.getPreviousSibling(); + if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { + if (previous.getNodeValue().trim().length() == 0) { + node.removeChild(previous); + } + } + node.removeChild(commentNode); + Node first = node.getFirstChild(); + if (first != null && first.getNextSibling() == null + && first.getNodeType() == Node.TEXT_NODE) { + if (first.getNodeValue().trim().length() == 0) { + node.removeChild(first); + } + } + } + } + } + } + + /** + * Returns the given property of the given DOM node, or null + * + * @param node the XML node to associate metadata with + * @param name the name of the property to look up + * @return the value stored with the given node and name, or null + */ + @Nullable + public static String getProperty( + @NonNull Node node, + @NonNull String name) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + String value = element.getAttributeNS(TOOLS_URI, name); + if (value != null && value.isEmpty()) { + value = null; + } + + return value; + } + + return null; + } + + /** + * Sets the given property of the given DOM node to a given value, or if null clears + * the property. + * + * @param editor the editor associated with the property + * @param node the XML node to associate metadata with + * @param name the name of the property to set + * @param value the value to store for the given node and name, or null to remove it + */ + public static void setProperty( + @NonNull final AndroidXmlEditor editor, + @NonNull final Node node, + @NonNull final String name, + @Nullable final String value) { + // Clear out the old metadata + clearLegacyComment(node); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + final Element element = (Element) node; + final String undoLabel = "Bind View"; + AdtUtils.setToolsAttribute(editor, element, undoLabel, name, value, + false /*reveal*/, false /*append*/); + + // Also apply the same layout to any corresponding elements in other configurations + // of this layout. + final IFile file = editor.getInputFile(); + if (file != null) { + final List<IFile> variations = AdtUtils.getResourceVariations(file, false); + if (variations.isEmpty()) { + return; + } + Display display = AdtPlugin.getDisplay(); + WorkbenchJob job = new WorkbenchJob(display, "Update alternate views") { + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + for (IFile variation : variations) { + if (variation.equals(file)) { + continue; + } + try { + // If the corresponding file is open in the IDE, use the + // editor version instead + if (!AdtPrefs.getPrefs().isSharedLayoutEditor()) { + if (setPropertyInEditor(undoLabel, variation, element, name, + value)) { + return Status.OK_STATUS; + } + } + + boolean old = editor.getIgnoreXmlUpdate(); + try { + editor.setIgnoreXmlUpdate(true); + setPropertyInFile(undoLabel, variation, element, name, value); + } finally { + editor.setIgnoreXmlUpdate(old); + } + } catch (Exception e) { + AdtPlugin.log(e, variation.getFullPath().toOSString()); + } + } + return Status.OK_STATUS; + } + + }; + job.setSystem(true); + job.schedule(); + } + } + } + + private static boolean setPropertyInEditor( + @NonNull String undoLabel, + @NonNull IFile variation, + @NonNull final Element equivalentElement, + @NonNull final String name, + @Nullable final String value) { + Collection<IEditorPart> editors = + AdtUtils.findEditorsFor(variation, false /*restore*/); + for (IEditorPart part : editors) { + AndroidXmlEditor editor = AdtUtils.getXmlEditor(part); + if (editor != null) { + Document doc = DomUtilities.getDocument(editor); + if (doc != null) { + Element element = DomUtilities.findCorresponding(equivalentElement, doc); + if (element != null) { + AdtUtils.setToolsAttribute(editor, element, undoLabel, name, + value, false /*reveal*/, false /*append*/); + if (part instanceof GraphicalEditorPart) { + GraphicalEditorPart g = (GraphicalEditorPart) part; + g.recomputeLayout(); + g.getCanvasControl().redraw(); + } + return true; + } + } + } + } + + return false; + } + + private static boolean setPropertyInFile( + @NonNull String undoLabel, + @NonNull IFile variation, + @NonNull final Element element, + @NonNull final String name, + @Nullable final String value) { + Document doc = DomUtilities.getDocument(variation); + if (doc != null && element.getOwnerDocument() != doc) { + Element other = DomUtilities.findCorresponding(element, doc); + if (other != null) { + AdtUtils.setToolsAttribute(variation, other, undoLabel, + name, value, false); + + return true; + } + } + + return false; + } + + /** Strips out @layout/ or @android:layout/ from the given layout reference */ + private static String stripLayoutPrefix(String layout) { + if (layout.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) { + layout = layout.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); + } else if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { + layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); + } + + return layout; + } + + /** + * Creates an {@link AdapterBinding} for the given view object, or null if the user + * has not yet chosen a target layout to use for the given AdapterView. + * + * @param viewObject the view object to create an adapter binding for + * @param map a map containing tools attribute metadata + * @return a binding, or null + */ + @Nullable + public static AdapterBinding getNodeBinding( + @Nullable Object viewObject, + @NonNull Map<String, String> map) { + String header = map.get(KEY_LV_HEADER); + String footer = map.get(KEY_LV_FOOTER); + String layout = map.get(KEY_LV_ITEM); + if (layout != null || header != null || footer != null) { + int count = 12; + return getNodeBinding(viewObject, header, footer, layout, count); + } + + return null; + } + + /** + * Creates an {@link AdapterBinding} for the given view object, or null if the user + * has not yet chosen a target layout to use for the given AdapterView. + * + * @param viewObject the view object to create an adapter binding for + * @param uiNode the ui node corresponding to the view object + * @return a binding, or null + */ + @Nullable + public static AdapterBinding getNodeBinding( + @Nullable Object viewObject, + @NonNull UiViewElementNode uiNode) { + Node xmlNode = uiNode.getXmlNode(); + + String header = getProperty(xmlNode, KEY_LV_HEADER); + String footer = getProperty(xmlNode, KEY_LV_FOOTER); + String layout = getProperty(xmlNode, KEY_LV_ITEM); + if (layout != null || header != null || footer != null) { + int count = 12; + // If we're dealing with a grid view, multiply the list item count + // by the number of columns to ensure we have enough items + if (xmlNode instanceof Element && xmlNode.getNodeName().endsWith(GRID_VIEW)) { + Element element = (Element) xmlNode; + String columns = element.getAttributeNS(ANDROID_URI, ATTR_NUM_COLUMNS); + int multiplier = 2; + if (columns != null && columns.length() > 0 && + !columns.equals(VALUE_AUTO_FIT)) { + try { + int c = Integer.parseInt(columns); + if (c >= 1 && c <= 10) { + multiplier = c; + } + } catch (NumberFormatException nufe) { + // some unexpected numColumns value: just stick with 2 columns for + // preview purposes + } + } + count *= multiplier; + } + + return getNodeBinding(viewObject, header, footer, layout, count); + } + + return null; + } + + private static AdapterBinding getNodeBinding(Object viewObject, + String header, String footer, String layout, int count) { + if (layout != null || header != null || footer != null) { + AdapterBinding binding = new AdapterBinding(count); + + if (header != null) { + boolean isFramework = header.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); + binding.addHeader(new ResourceReference(stripLayoutPrefix(header), + isFramework)); + } + + if (footer != null) { + boolean isFramework = footer.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); + binding.addFooter(new ResourceReference(stripLayoutPrefix(footer), + isFramework)); + } + + if (layout != null) { + boolean isFramework = layout.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); + if (isFramework) { + layout = layout.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); + } else if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { + layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); + } + + binding.addItem(new DataBindingItem(layout, isFramework, 1)); + } else if (viewObject != null) { + String listFqcn = ProjectCallback.getListAdapterViewFqcn(viewObject.getClass()); + if (listFqcn != null) { + if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) { + binding.addItem( + new DataBindingItem(DEFAULT_EXPANDABLE_LIST_ITEM, + true /* isFramework */, 1)); + } else { + binding.addItem( + new DataBindingItem(DEFAULT_LIST_ITEM, + true /* isFramework */, 1)); + } + } + } else { + binding.addItem( + new DataBindingItem(DEFAULT_LIST_ITEM, + true /* isFramework */, 1)); + } + return binding; + } + + return null; + } +} |