aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.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/refactoring/ChangeLayoutRefactoring.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java657
1 files changed, 657 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java
new file mode 100644
index 000000000..d8c85aab5
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java
@@ -0,0 +1,657 @@
+/*
+ * 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.refactoring;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
+import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
+import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_ORIENTATION;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.FQCN_GESTURE_OVERLAY_VIEW;
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT;
+import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT;
+import static com.android.SdkConstants.FQCN_RELATIVE_LAYOUT;
+import static com.android.SdkConstants.FQCN_TABLE_LAYOUT;
+import static com.android.SdkConstants.GESTURE_OVERLAY_VIEW;
+import static com.android.SdkConstants.LINEAR_LAYOUT;
+import static com.android.SdkConstants.TABLE_ROW;
+import static com.android.SdkConstants.VALUE_FALSE;
+import static com.android.SdkConstants.VALUE_VERTICAL;
+import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ViewHierarchy;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.viewers.ITreeSelection;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Converts the selected layout into a layout of a different type.
+ */
+@SuppressWarnings("restriction") // XML model
+public class ChangeLayoutRefactoring extends VisualRefactoring {
+ private static final String KEY_TYPE = "type"; //$NON-NLS-1$
+ private static final String KEY_FLATTEN = "flatten"; //$NON-NLS-1$
+
+ private String mTypeFqcn;
+ private String mInitializedAttributes;
+ private boolean mFlatten;
+
+ /**
+ * This constructor is solely used by {@link Descriptor},
+ * to replay a previous refactoring.
+ * @param arguments argument map created by #createArgumentMap.
+ */
+ ChangeLayoutRefactoring(Map<String, String> arguments) {
+ super(arguments);
+ mTypeFqcn = arguments.get(KEY_TYPE);
+ mFlatten = Boolean.parseBoolean(arguments.get(KEY_FLATTEN));
+ }
+
+ @VisibleForTesting
+ ChangeLayoutRefactoring(List<Element> selectedElements, LayoutEditorDelegate delegate) {
+ super(selectedElements, delegate);
+ }
+
+ public ChangeLayoutRefactoring(
+ IFile file,
+ LayoutEditorDelegate delegate,
+ ITextSelection selection,
+ ITreeSelection treeSelection) {
+ super(file, delegate, selection, treeSelection);
+ }
+
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ RefactoringStatus status = new RefactoringStatus();
+
+ try {
+ pm.beginTask("Checking preconditions...", 2);
+
+ if (mSelectionStart == -1 || mSelectionEnd == -1) {
+ status.addFatalError("No selection to convert");
+ return status;
+ }
+
+ if (mElements.size() != 1) {
+ status.addFatalError("Select precisely one layout to convert");
+ return status;
+ }
+
+ pm.worked(1);
+ return status;
+
+ } finally {
+ pm.done();
+ }
+ }
+
+ @Override
+ protected VisualRefactoringDescriptor createDescriptor() {
+ String comment = getName();
+ return new Descriptor(
+ mProject.getName(), //project
+ comment, //description
+ comment, //comment
+ createArgumentMap());
+ }
+
+ @Override
+ protected Map<String, String> createArgumentMap() {
+ Map<String, String> args = super.createArgumentMap();
+ args.put(KEY_TYPE, mTypeFqcn);
+ args.put(KEY_FLATTEN, Boolean.toString(mFlatten));
+
+ return args;
+ }
+
+ @Override
+ public String getName() {
+ return "Change Layout";
+ }
+
+ void setType(String typeFqcn) {
+ mTypeFqcn = typeFqcn;
+ }
+
+ void setInitializedAttributes(String initializedAttributes) {
+ mInitializedAttributes = initializedAttributes;
+ }
+
+ void setFlatten(boolean flatten) {
+ mFlatten = flatten;
+ }
+
+ @Override
+ protected List<Element> initElements() {
+ List<Element> elements = super.initElements();
+
+ // Don't convert a root GestureOverlayView; convert its child. This looks for
+ // gesture overlays, and if found, it generates a new child list where the gesture
+ // overlay children are replaced by their first element children
+ for (Element element : elements) {
+ String tagName = element.getTagName();
+ if (tagName.equals(GESTURE_OVERLAY_VIEW)
+ || tagName.equals(FQCN_GESTURE_OVERLAY_VIEW)) {
+ List<Element> replacement = new ArrayList<Element>(elements.size());
+ for (Element e : elements) {
+ tagName = e.getTagName();
+ if (tagName.equals(GESTURE_OVERLAY_VIEW)
+ || tagName.equals(FQCN_GESTURE_OVERLAY_VIEW)) {
+ NodeList children = e.getChildNodes();
+ Element first = null;
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ first = (Element) node;
+ break;
+ }
+ }
+ if (first != null) {
+ e = first;
+ }
+ }
+ replacement.add(e);
+ }
+ return replacement;
+ }
+ }
+
+ return elements;
+ }
+
+ @Override
+ protected @NonNull List<Change> computeChanges(IProgressMonitor monitor) {
+ String name = getViewClass(mTypeFqcn);
+
+ IFile file = mDelegate.getEditor().getInputFile();
+ List<Change> changes = new ArrayList<Change>();
+ if (file == null) {
+ return changes;
+ }
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ change.setTextType(EXT_XML);
+ changes.add(change);
+
+ String text = getText(mSelectionStart, mSelectionEnd);
+ Element layout = getPrimaryElement();
+ String oldName = layout.getNodeName();
+ int open = text.indexOf(oldName);
+ int close = text.lastIndexOf(oldName);
+
+ if (open != -1 && close != -1) {
+ int oldLength = oldName.length();
+ rootEdit.addChild(new ReplaceEdit(mSelectionStart + open, oldLength, name));
+ if (close != open) { // Gracefully handle <FooLayout/>
+ rootEdit.addChild(new ReplaceEdit(mSelectionStart + close, oldLength, name));
+ }
+ }
+
+ String oldId = getId(layout);
+ String newId = ensureIdMatchesType(layout, mTypeFqcn, rootEdit);
+ // Update any layout references to the old id with the new id
+ if (oldId != null && newId != null) {
+ IStructuredModel model = mDelegate.getEditor().getModelForRead();
+ try {
+ IStructuredDocument doc = model.getStructuredDocument();
+ if (doc != null) {
+ List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc,
+ mSelectionStart,
+ mSelectionEnd, oldId, newId);
+ for (TextEdit edit : replaceIds) {
+ rootEdit.addChild(edit);
+ }
+ }
+ } finally {
+ model.releaseFromRead();
+ }
+ }
+
+ String oldType = getOldType();
+ String newType = mTypeFqcn;
+
+ if (newType.equals(FQCN_RELATIVE_LAYOUT)) {
+ if (oldType.equals(FQCN_LINEAR_LAYOUT) && !mFlatten) {
+ // Hand-coded conversion specifically tailored for linear to relative, provided
+ // there is no hierarchy flattening
+ // TODO: use the RelativeLayoutConversionHelper for this; it does a better job
+ // analyzing gravities etc.
+ convertLinearToRelative(rootEdit);
+ removeUndefinedAttrs(rootEdit, layout);
+ addMissingWrapContentAttributes(rootEdit, layout, oldType, newType, null);
+ } else {
+ // Generic conversion to relative - can also flatten the hierarchy
+ convertAnyToRelative(rootEdit, oldType, newType);
+ // This already handles removing undefined layout attributes -- right?
+ //removeUndefinedLayoutAttrs(rootEdit, layout);
+ }
+ } else if (newType.equals(FQCN_GRID_LAYOUT)) {
+ convertAnyToGridLayout(rootEdit);
+ // Layout attributes on children have already been removed as part of conversion
+ // during the flattening
+ removeUndefinedAttrs(rootEdit, layout, false /*removeLayoutAttrs*/);
+ } else if (oldType.equals(FQCN_RELATIVE_LAYOUT) && newType.equals(FQCN_LINEAR_LAYOUT)) {
+ convertRelativeToLinear(rootEdit);
+ removeUndefinedAttrs(rootEdit, layout);
+ addMissingWrapContentAttributes(rootEdit, layout, oldType, newType, null);
+ } else if (oldType.equals(FQCN_LINEAR_LAYOUT) && newType.equals(FQCN_TABLE_LAYOUT)) {
+ convertLinearToTable(rootEdit);
+ removeUndefinedAttrs(rootEdit, layout);
+ addMissingWrapContentAttributes(rootEdit, layout, oldType, newType, null);
+ } else {
+ convertGeneric(rootEdit, oldType, newType, layout);
+ }
+
+ if (mInitializedAttributes != null && mInitializedAttributes.length() > 0) {
+ String namespace = getAndroidNamespacePrefix();
+ for (String s : mInitializedAttributes.split(",")) { //$NON-NLS-1$
+ String[] nameValue = s.split("="); //$NON-NLS-1$
+ String attribute = nameValue[0];
+ String value = nameValue[1];
+ String prefix = null;
+ String namespaceUri = null;
+ if (attribute.startsWith(SdkConstants.ANDROID_NS_NAME_PREFIX)) {
+ prefix = namespace;
+ namespaceUri = ANDROID_URI;
+ attribute = attribute.substring(SdkConstants.ANDROID_NS_NAME_PREFIX.length());
+ }
+ setAttribute(rootEdit, layout, namespaceUri,
+ prefix, attribute, value);
+ }
+ }
+
+ if (AdtPrefs.getPrefs().getFormatGuiXml()) {
+ MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT);
+ if (formatted != null) {
+ rootEdit = formatted;
+ }
+ }
+ change.setEdit(rootEdit);
+
+ return changes;
+ }
+
+ /** Checks whether we need to add any missing attributes on the elements */
+ private void addMissingWrapContentAttributes(MultiTextEdit rootEdit, Element layout,
+ String oldType, String newType, Set<Element> skip) {
+ if (oldType.equals(FQCN_GRID_LAYOUT) && !newType.equals(FQCN_GRID_LAYOUT)) {
+ String namespace = getAndroidNamespacePrefix();
+
+ for (Element child : DomUtilities.getChildren(layout)) {
+ if (skip != null && skip.contains(child)) {
+ continue;
+ }
+
+ if (!child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH)) {
+ setAttribute(rootEdit, child, ANDROID_URI,
+ namespace, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT);
+ }
+ if (!child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT)) {
+ setAttribute(rootEdit, child, ANDROID_URI,
+ namespace, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT);
+ }
+ }
+ }
+ }
+
+ /** Hand coded conversion from a LinearLayout to a TableLayout */
+ private void convertLinearToTable(MultiTextEdit rootEdit) {
+ // This is pretty easy; just switch the root tag (already done by the initial generic
+ // conversion) and then convert all the children into <TableRow> elements.
+ // Finally, get rid of the orientation attribute, if any.
+ Element layout = getPrimaryElement();
+ removeOrientationAttribute(rootEdit, layout);
+
+ NodeList children = layout.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+ if (node instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) node;
+ int start = region.getStartOffset();
+ int end = region.getEndOffset();
+ String text = getText(start, end);
+ String oldName = child.getNodeName();
+ if (oldName.equals(LINEAR_LAYOUT)) {
+ removeOrientationAttribute(rootEdit, child);
+ int open = text.indexOf(oldName);
+ int close = text.lastIndexOf(oldName);
+
+ if (open != -1 && close != -1) {
+ int oldLength = oldName.length();
+ rootEdit.addChild(new ReplaceEdit(mSelectionStart + open, oldLength,
+ TABLE_ROW));
+ if (close != open) { // Gracefully handle <FooLayout/>
+ rootEdit.addChild(new ReplaceEdit(mSelectionStart + close,
+ oldLength, TABLE_ROW));
+ }
+ }
+ } // else: WRAP in TableLayout!
+ }
+ }
+ }
+ }
+
+ /** Hand coded conversion from a LinearLayout to a RelativeLayout */
+ private void convertLinearToRelative(MultiTextEdit rootEdit) {
+ // This can be done accurately.
+ Element layout = getPrimaryElement();
+ // Horizontal is the default, so if no value is specified it is horizontal.
+ boolean isVertical = VALUE_VERTICAL.equals(layout.getAttributeNS(ANDROID_URI,
+ ATTR_ORIENTATION));
+
+ String attributePrefix = getAndroidNamespacePrefix();
+
+ // TODO: Consider gravity of each element
+ // TODO: Consider weight of each element
+ // Right now it simply makes a single attachment to keep the order.
+
+ if (isVertical) {
+ // Align each child to the bottom and left of its parent
+ NodeList children = layout.getChildNodes();
+ String prevId = null;
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+ String id = ensureHasId(rootEdit, child, null);
+ if (prevId != null) {
+ setAttribute(rootEdit, child, ANDROID_URI, attributePrefix,
+ ATTR_LAYOUT_BELOW, prevId);
+ }
+ prevId = id;
+ }
+ }
+ } else {
+ // Align each child to the left
+ NodeList children = layout.getChildNodes();
+ boolean isBaselineAligned =
+ !VALUE_FALSE.equals(layout.getAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED));
+
+ String prevId = null;
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+ String id = ensureHasId(rootEdit, child, null);
+ if (prevId != null) {
+ setAttribute(rootEdit, child, ANDROID_URI, attributePrefix,
+ ATTR_LAYOUT_TO_RIGHT_OF, prevId);
+ if (isBaselineAligned) {
+ setAttribute(rootEdit, child, ANDROID_URI, attributePrefix,
+ ATTR_LAYOUT_ALIGN_BASELINE, prevId);
+ }
+ }
+ prevId = id;
+ }
+ }
+ }
+ }
+
+ /** Strips out the android:orientation attribute from the given linear layout element */
+ private void removeOrientationAttribute(MultiTextEdit rootEdit, Element layout) {
+ assert layout.getTagName().equals(LINEAR_LAYOUT);
+ removeAttribute(rootEdit, layout, ANDROID_URI, ATTR_ORIENTATION);
+ }
+
+ /**
+ * Hand coded conversion from a RelativeLayout to a LinearLayout
+ *
+ * @param rootEdit the root multi text edit to add edits to
+ */
+ private void convertRelativeToLinear(MultiTextEdit rootEdit) {
+ // This is going to be lossy...
+ // TODO: Attempt to "order" the items based on their visual positions
+ // and insert them in that order in the LinearLayout.
+ // TODO: Possibly use nesting if necessary, by spatial subdivision,
+ // to accomplish roughly the same layout as the relative layout specifies.
+ }
+
+ /**
+ * Hand coded -generic- conversion from one layout to another. This is not going to be
+ * an accurate layout transformation; instead it simply migrates the layout attributes
+ * that are supported, and adds defaults for any new required layout attributes. In
+ * addition, it attempts to order the children visually based on where they fit in a
+ * rendering. (Unsupported layout attributes will be removed by the caller at the
+ * end.)
+ * <ul>
+ * <li>Try to handle nesting. Converting a *hierarchy* of layouts into a flatter
+ * layout for powerful layouts that support it, like RelativeLayout.
+ * <li>Try to do automatic "inference" about the layout. I can render it and look at
+ * the ViewInfo positions and sizes. I can render it multiple times, at different
+ * sizes, to infer "stretchiness" and "weight" properties of the children.
+ * <li>Try to do indirect transformations. E.g. if I can go from A to B, and B to C,
+ * then an attempt to go from A to C should perform conversions A to B and then B to
+ * C.
+ * </ul>
+ *
+ * @param rootEdit the root multi text edit to add edits to
+ * @param oldType the fully qualified class name of the layout type we are converting
+ * from
+ * @param newType the fully qualified class name of the layout type we are converting
+ * to
+ * @param layout the layout to be converted
+ */
+ private void convertGeneric(MultiTextEdit rootEdit, String oldType, String newType,
+ Element layout) {
+ // TODO: Add hooks for 3rd party conversions getting registered through the
+ // IViewRule interface.
+
+ // For now we simply go with the default behavior, which is to just strip the
+ // layout attributes that aren't supported.
+ removeUndefinedAttrs(rootEdit, layout);
+ addMissingWrapContentAttributes(rootEdit, layout, oldType, newType, null);
+ }
+
+ /**
+ * Removes all the unavailable attributes after a conversion, both on the
+ * layout element itself as well as the layout attributes of any of the
+ * children
+ */
+ private void removeUndefinedAttrs(MultiTextEdit rootEdit, Element layout) {
+ removeUndefinedAttrs(rootEdit, layout, true /*removeLayoutAttrs*/);
+ }
+
+ private void removeUndefinedAttrs(MultiTextEdit rootEdit, Element layout,
+ boolean removeLayoutAttrs) {
+ ViewElementDescriptor descriptor = getElementDescriptor(mTypeFqcn);
+ if (descriptor == null) {
+ return;
+ }
+
+ if (removeLayoutAttrs) {
+ Set<String> defined = new HashSet<String>();
+ AttributeDescriptor[] layoutAttributes = descriptor.getLayoutAttributes();
+ for (AttributeDescriptor attribute : layoutAttributes) {
+ defined.add(attribute.getXmlLocalName());
+ }
+
+ NodeList children = layout.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+
+ List<Attr> attributes = findLayoutAttributes(child);
+ for (Attr attribute : attributes) {
+ String name = attribute.getLocalName();
+ if (!defined.contains(name)) {
+ // Remove it
+ try {
+ removeAttribute(rootEdit, child, attribute.getNamespaceURI(), name);
+ } catch (MalformedTreeException mte) {
+ // Sometimes refactoring has modified attribute; not removing
+ // it is non-fatal so just warn instead of letting refactoring
+ // operation abort
+ AdtPlugin.log(IStatus.WARNING,
+ "Could not remove unsupported attribute %1$s; " + //$NON-NLS-1$
+ "already modified during refactoring?", //$NON-NLS-1$
+ attribute.getLocalName());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Also remove the unavailable attributes (not layout attributes) on the
+ // converted element
+ Set<String> defined = new HashSet<String>();
+ AttributeDescriptor[] attributes = descriptor.getAttributes();
+ for (AttributeDescriptor attribute : attributes) {
+ defined.add(attribute.getXmlLocalName());
+ }
+
+ // Remove undefined attributes on the layout element itself
+ NamedNodeMap attributeMap = layout.getAttributes();
+ for (int i = 0, n = attributeMap.getLength(); i < n; i++) {
+ Node attributeNode = attributeMap.item(i);
+
+ String name = attributeNode.getLocalName();
+ if (!name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
+ && ANDROID_URI.equals(attributeNode.getNamespaceURI())) {
+ if (!defined.contains(name)) {
+ // Remove it
+ removeAttribute(rootEdit, layout, ANDROID_URI, name);
+ }
+ }
+ }
+ }
+
+ /** Hand coded conversion from any layout to a RelativeLayout */
+ private void convertAnyToRelative(MultiTextEdit rootEdit, String oldType, String newType) {
+ // To perform a conversion from any other layout type, including nested conversion,
+ Element layout = getPrimaryElement();
+ CanvasViewInfo rootView = mRootView;
+ if (rootView == null) {
+ LayoutCanvas canvas = mDelegate.getGraphicalEditor().getCanvasControl();
+ ViewHierarchy viewHierarchy = canvas.getViewHierarchy();
+ rootView = viewHierarchy.getRoot();
+ }
+
+ RelativeLayoutConversionHelper helper =
+ new RelativeLayoutConversionHelper(this, layout, mFlatten, rootEdit, rootView);
+ helper.convertToRelative();
+ List<Element> deletedElements = helper.getDeletedElements();
+ Set<Element> deleted = null;
+ if (deletedElements != null && deletedElements.size() > 0) {
+ deleted = new HashSet<Element>(deletedElements);
+ }
+ addMissingWrapContentAttributes(rootEdit, layout, oldType, newType, deleted);
+ }
+
+ /** Hand coded conversion from any layout to a GridLayout */
+ private void convertAnyToGridLayout(MultiTextEdit rootEdit) {
+ // To perform a conversion from any other layout type, including nested conversion,
+ Element layout = getPrimaryElement();
+ CanvasViewInfo rootView = mRootView;
+ if (rootView == null) {
+ LayoutCanvas canvas = mDelegate.getGraphicalEditor().getCanvasControl();
+ ViewHierarchy viewHierarchy = canvas.getViewHierarchy();
+ rootView = viewHierarchy.getRoot();
+ }
+
+ GridLayoutConverter converter = new GridLayoutConverter(this, layout, mFlatten,
+ rootEdit, rootView);
+ converter.convertToGridLayout();
+ }
+
+ public static class Descriptor extends VisualRefactoringDescriptor {
+ public Descriptor(String project, String description, String comment,
+ Map<String, String> arguments) {
+ super("com.android.ide.eclipse.adt.refactoring.convert", //$NON-NLS-1$
+ project, description, comment, arguments);
+ }
+
+ @Override
+ protected Refactoring createRefactoring(Map<String, String> args) {
+ return new ChangeLayoutRefactoring(args);
+ }
+ }
+
+ String getOldType() {
+ Element primary = getPrimaryElement();
+ if (primary != null) {
+ String oldType = primary.getTagName();
+ if (oldType.indexOf('.') == -1) {
+ oldType = ANDROID_WIDGET_PREFIX + oldType;
+ }
+ return oldType;
+ }
+
+ return null;
+ }
+
+ @VisibleForTesting
+ protected CanvasViewInfo mRootView;
+
+ @VisibleForTesting
+ public void setRootView(CanvasViewInfo rootView) {
+ mRootView = rootView;
+ }
+
+ @Override
+ VisualRefactoringWizard createWizard() {
+ return new ChangeLayoutWizard(this, mDelegate);
+ }
+}