| /* |
| * 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); |
| } |
| } |