diff options
56 files changed, 1853 insertions, 72 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 9c3f208..82f4e05 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -686,6 +686,15 @@ tooltip="Extracts Views as Included Layout"> </action> <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction" + definitionId="com.android.ide.eclipse.adt.refactoring.extract.style" + id="com.android.ide.eclipse.adt.actions.ExtractStyle" + label="Extract Style..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Extracts Styles"> + </action> + <action class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction" definitionId="com.android.ide.eclipse.adt.refactoring.wrapin" id="com.android.ide.eclipse.adt.actions.WrapIn" @@ -829,6 +838,12 @@ </command> <command categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Extract Styles" + id="com.android.ide.eclipse.adt.refactoring.extract.style" + name="Extract Styles"> + </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" description="Wraps Views in a New Container" id="com.android.ide.eclipse.adt.refactoring.wrapin" name="Wrap in Container"> @@ -857,6 +872,10 @@ id="com.android.ide.eclipse.adt.refactoring.extract.include"> </contribution> <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleContribution" + id="com.android.ide.eclipse.adt.refactoring.extract.style"> + </contribution> + <contribution class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInContribution" id="com.android.ide.eclipse.adt.refactoring.wrapin"> </contribution> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index 092b0a5..73cb229 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -51,6 +51,7 @@ public class LayoutConstants { public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$ public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ + public static final String ATTR_HINT = "hint"; //$NON-NLS-1$ public static final String ATTR_ID = "id"; //$NON-NLS-1$ public static final String ATTR_STYLE = "style"; //$NON-NLS-1$ public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 6018d17..43fd823 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -1810,7 +1810,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { } /** - * Opens the given file and shows the given (optional) region + * Opens the given file and shows the given (optional) region in the editor (or + * if no region is specified, opens the editor tab.) * * @param file the file to be opened * @param region an optional region which if set will be selected and shown to the @@ -1818,6 +1819,20 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { * @throws PartInitException if something goes wrong */ public static void openFile(IFile file, IRegion region) throws PartInitException { + openFile(file, region, true); + } + + /** + * Opens the given file and shows the given (optional) region + * + * @param file the file to be opened + * @param region an optional region which if set will be selected and shown to the + * user + * @param showEditorTab if true, front the editor tab after opening the file + * @throws PartInitException if something goes wrong + */ + public static void openFile(IFile file, IRegion region, boolean showEditorTab) + throws PartInitException { IWorkbench workbench = PlatformUI.getWorkbench(); if (workbench == null) { return; @@ -1835,7 +1850,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor; if (region != null) { editor.show(region.getOffset(), region.getLength()); - } else { + } else if (showEditorTab) { editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index b862ada..bccc7d0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java index 2bb8901..2a70dd0 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java @@ -526,19 +526,41 @@ public class CanvasViewInfo implements IPropertySource { * This method will build up a set of {@link CanvasViewInfo} that corresponds to the * actual <b>selectable</b> views (which are also shown in the Outline). * + * @param layoutlib5 if true, the {@link ViewInfo} hierarchy was created by layoutlib + * version 5 or higher, which means this algorithm can make certain assumptions + * (for example that {@code <merge>} siblings will provide {@link MergeCookie} + * references, so we don't have to search for them.) * @param root the root {@link ViewInfo} to build from * @return a {@link CanvasViewInfo} hierarchy */ - public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { - return new Builder().create(root); + public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root, boolean layoutlib5) { + return new Builder(layoutlib5).create(root); } /** Builder object which walks over a tree of {@link ViewInfo} objects and builds * up a corresponding {@link CanvasViewInfo} hierarchy. */ private static class Builder { - private Map<UiViewElementNode,List<CanvasViewInfo>> mMergeNodeMap; + public Builder(boolean layoutlib5) { + mLayoutLib5 = layoutlib5; + } - public Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { + /** + * The mapping from nodes that have a {@code <merge>} as a parent in the node + * model to their corresponding views + */ + private Map<UiViewElementNode, List<CanvasViewInfo>> mMergeNodeMap; + + /** + * Whether the ViewInfos are provided by a layout library that is version 5 or + * later, since that will allow us to take several shortcuts + */ + private boolean mLayoutLib5; + + /** + * Creates a hierarchy of {@link CanvasViewInfo} objects and merge bounding + * rectangles from the given {@link ViewInfo} hierarchy + */ + private Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { Object cookie = root.getCookie(); if (cookie == null) { // Special case: If the root-most view does not have a view cookie, @@ -717,10 +739,24 @@ public class CanvasViewInfo implements IPropertySource { parentX += viewInfo.getLeft(); parentY += viewInfo.getTop(); + List<ViewInfo> children = viewInfo.getChildren(); + + if (mLayoutLib5) { + for (ViewInfo child : children) { + Object cookie = child.getCookie(); + if (cookie instanceof UiViewElementNode || cookie instanceof MergeCookie) { + CanvasViewInfo childView = createSubtree(view, child, + parentX, parentY); + view.addChild(childView); + } // else: null cookies, adapter item references, etc: No child views. + } + + return view; + } + // See if we have any missing keys at this level int missingNodes = 0; int mergeNodes = 0; - List<ViewInfo> children = viewInfo.getChildren(); for (ViewInfo child : children) { // Only use children which have a ViewKey of the correct type. // We can't interact with those when they have a null key or @@ -751,7 +787,9 @@ public class CanvasViewInfo implements IPropertySource { // embedded_layout rendering, or we are including a view with a <merge> // as the root element. - String containerName = view.getUiViewNode().getDescriptor().getXmlLocalName(); + UiViewElementNode uiViewNode = view.getUiViewNode(); + String containerName = uiViewNode != null + ? uiViewNode.getDescriptor().getXmlLocalName() : ""; //$NON-NLS-1$ if (containerName.equals(LayoutDescriptors.VIEW_INCLUDE)) { // This is expected -- we don't WANT to get node keys for the content // of an include since it's in a different file and should be treated @@ -764,9 +802,11 @@ public class CanvasViewInfo implements IPropertySource { // that there are <merge> tags which are doing surprising things // to the view hierarchy LinkedList<UiViewElementNode> unused = new LinkedList<UiViewElementNode>(); - for (UiElementNode child : view.getUiViewNode().getUiChildren()) { - if (child instanceof UiViewElementNode) { - unused.addLast((UiViewElementNode) child); + if (uiViewNode != null) { + for (UiElementNode child : uiViewNode.getUiChildren()) { + if (child instanceof UiViewElementNode) { + unused.addLast((UiViewElementNode) child); + } } } for (ViewInfo child : children) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index a0db1fb..1857602 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -28,6 +28,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction; import org.eclipse.jface.action.Action; @@ -219,6 +220,7 @@ import java.util.regex.Pattern; // or on an included view, or on a non-contiguous selection mMenuManager.insertBefore(endId, new Separator()); mMenuManager.insertBefore(endId, ExtractIncludeAction.create(mEditor)); + mMenuManager.insertBefore(endId, ExtractStyleAction.create(mEditor)); mMenuManager.insertBefore(endId, WrapInAction.create(mEditor)); if (selection.size() == 1 && (selection.get(0).isLayout() || selection.get(0).getViewInfo().getName().equals(FQCN_GESTURE_OVERLAY_VIEW))) { 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 47d4f22..0a65865 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 @@ -1053,7 +1053,7 @@ public class GraphicalEditorPart extends EditorPart new StaticRenderSession( Result.Status.SUCCESS.createResult(), null /*rootViewInfo*/, null /*image*/), - null /*explodeNodes*/); + null /*explodeNodes*/, true /* layoutlib5 */); return; } @@ -1339,7 +1339,8 @@ public class GraphicalEditorPart extends EditorPart explodeNodes, null /*custom background*/, false /*no decorations*/, logger, mIncludedWithin, renderingMode); - canvas.setSession(session, explodeNodes); + boolean layoutlib5 = layoutLib.supports(Capability.EMBEDDED_LAYOUT); + canvas.setSession(session, explodeNodes, layoutlib5); // update the UiElementNode with the layout info. if (session != null && session.getResult().isSuccess() == false) { @@ -1587,7 +1588,7 @@ public class GraphicalEditorPart extends EditorPart return null; } - ResourceResolver createResolver() { + public ResourceResolver createResolver() { String theme = mConfigComposite.getTheme(); boolean isProjectTheme = mConfigComposite.isProjectTheme(); Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 277c194..b54e3d1 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -214,6 +214,11 @@ public class LayoutCanvas extends Canvas { private final GestureManager mGestureManager = new GestureManager(this); /** + * When set, performs a zoom-to-fit when the next rendering image arrives. + */ + private boolean mZoomFitNextImage; + + /** * Native clipboard support. */ private ClipboardSupport mClipboardSupport; @@ -244,6 +249,8 @@ public class LayoutCanvas extends Canvas { } catch (NumberFormatException nfe) { // Ignore - use zoom=100% } + } else { + mZoomFitNextImage = true; } } @@ -546,11 +553,12 @@ public class LayoutCanvas extends Canvas { * {@link #showInvisibleViews(boolean)}) where individual invisible nodes * are padded during certain interactions. */ - /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes) { + /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, + boolean layoutlib5) { // disable any hover clearHover(); - mViewHierarchy.setSession(session, explodedNodes); + mViewHierarchy.setSession(session, explodedNodes, layoutlib5); if (mViewHierarchy.isValid() && session != null) { Image image = mImageOverlay.setImage(session.getImage(), session.isAlphaChannelImage()); @@ -559,6 +567,16 @@ public class LayoutCanvas extends Canvas { if (image != null) { mHScale.setSize(image.getImageData().width, getClientArea().width); mVScale.setSize(image.getImageData().height, getClientArea().height); + if (mZoomFitNextImage) { + mZoomFitNextImage = false; + // Must be run asynchronously because getClientArea() returns 0 bounds + // when the editor is being initialized + getDisplay().asyncExec(new Runnable() { + public void run() { + setFitScale(true); + } + }); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java index 8624cd3..10625bb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java @@ -136,7 +136,8 @@ public class ViewHierarchy { * {@link LayoutCanvas#showInvisibleViews}) where individual invisible * nodes are padded during certain interactions. */ - /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes) { + /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, + boolean layoutlib5) { // replace the previous scene, so the previous scene must be disposed. if (mSession != null) { mSession.dispose(); @@ -157,7 +158,7 @@ public class ViewHierarchy { // via drag & drop, etc. if (hasMergeRoot()) { ViewInfo mergeRoot = createMergeInfo(session); - infos = CanvasViewInfo.create(mergeRoot); + infos = CanvasViewInfo.create(mergeRoot, layoutlib5); } else { infos = null; } @@ -165,11 +166,12 @@ public class ViewHierarchy { if (rootList.size() > 1 && hasMergeRoot()) { ViewInfo mergeRoot = createMergeInfo(session); mergeRoot.setChildren(rootList); - infos = CanvasViewInfo.create(mergeRoot); + infos = CanvasViewInfo.create(mergeRoot, layoutlib5); } else { ViewInfo root = rootList.get(0); + if (root != null) { - infos = CanvasViewInfo.create(root); + infos = CanvasViewInfo.create(root, layoutlib5); if (DUMP_INFO) { dump(root, 0); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java new file mode 100644 index 0000000..4eef6b8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java @@ -0,0 +1,47 @@ +/* + * 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 com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Extract Style" menu item is invoked. + */ +public class ExtractStyleAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ExtractStyleRefactoring ref = new ExtractStyleRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ExtractStyleWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Extract Style...", editor, ExtractStyleAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java new file mode 100644 index 0000000..95fbdbc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java @@ -0,0 +1,40 @@ +/* + * 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 org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ExtractStyleContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ExtractStyleRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ExtractStyleRefactoring.Descriptor) { + return ((ExtractStyleRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java new file mode 100644 index 0000000..3e18fca --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java @@ -0,0 +1,538 @@ +/* + * 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.AndroidConstants.FD_RES_VALUES; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; +import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ITEM_TAG; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.PARENT_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ROOT_ELEMENT; +import static com.android.sdklib.SdkConstants.FD_RESOURCES; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +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.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +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.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Extracts the selection and writes it out as a separate layout file, then adds an + * include to that new layout file. Interactively asks the user for a new name for the + * layout. + * <p> + * Remaining work to do / Possible enhancements: + * <ul> + * <li>Optionally look in other files in the project and attempt to set style attributes + * in other cases where the style attributes match? + * <li>If the elements we are extracting from already contain a style attribute, set that + * style as the parent style of the current style? + * <li>Add a parent-style picker to the wizard (initialized with the above if applicable) + * <li>Pick up indentation settings from the XML module + * <li>Integrate with themes somehow -- make an option to have the extracted style go into + * the theme instead + * </ul> + */ +@SuppressWarnings("restriction") // XML model +public class ExtractStyleRefactoring extends VisualRefactoring { + private static final String KEY_NAME = "name"; //$NON-NLS-1$ + private static final String KEY_REMOVE_EXTRACTED = "removeextracted"; //$NON-NLS-1$ + private static final String KEY_REMOVE_ALL = "removeall"; //$NON-NLS-1$ + private static final String KEY_APPLY_STYLE = "applystyle"; //$NON-NLS-1$ + private static final String KEY_PARENT = "parent"; //$NON-NLS-1$ + private String mStyleName; + /** The name of the file in res/values/ that the style will be added to. Normally + * res/values/styles.xml - but unit tests pick other names */ + private String mStyleFileName = "styles.xml"; + /** Set a style reference on the extracted elements? */ + private boolean mApplyStyle; + /** Remove the attributes that were extracted? */ + private boolean mRemoveExtracted; + /** List of attributes chosen by the user to be extracted */ + private List<Attr> mChosenAttributes = new ArrayList<Attr>(); + /** Remove all attributes that match the extracted attributes names, regardless of value */ + private boolean mRemoveAll; + /** The parent style to extend */ + private String mParent; + /** The full list of available attributes in the refactoring */ + private Map<String, List<Attr>> mAvailableAttributes; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ExtractStyleRefactoring(Map<String, String> arguments) { + super(arguments); + mStyleName = arguments.get(KEY_NAME); + mRemoveExtracted = Boolean.parseBoolean(arguments.get(KEY_REMOVE_EXTRACTED)); + mRemoveAll = Boolean.parseBoolean(arguments.get(KEY_REMOVE_ALL)); + mApplyStyle = Boolean.parseBoolean(arguments.get(KEY_APPLY_STYLE)); + mParent = arguments.get(KEY_PARENT); + } + + public ExtractStyleRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + ExtractStyleRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to extract"); + return status; + } + + // This also ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to extract"); + 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_NAME, mStyleName); + args.put(KEY_REMOVE_EXTRACTED, Boolean.toString(mRemoveExtracted)); + args.put(KEY_REMOVE_ALL, Boolean.toString(mRemoveAll)); + args.put(KEY_APPLY_STYLE, Boolean.toString(mApplyStyle)); + args.put(KEY_PARENT, mParent); + + return args; + } + + @Override + public String getName() { + return "Extract Style"; + } + + void setStyleName(String styleName) { + mStyleName = styleName; + } + + void setStyleFileName(String styleFileName) { + mStyleFileName = styleFileName; + } + + void setChosenAttributes(List<Attr> attributes) { + mChosenAttributes = attributes; + } + + void setRemoveExtracted(boolean removeExtracted) { + mRemoveExtracted = removeExtracted; + } + + void setApplyStyle(boolean applyStyle) { + mApplyStyle = applyStyle; + } + + void setRemoveAll(boolean removeAll) { + mRemoveAll = removeAll; + } + + void setParent(String parent) { + mParent = parent; + } + + // ---- Actual implementation of Extract Style modification computation ---- + + /** + * Returns two items: a map from attribute name to a list of attribute nodes of that + * name, and a subset of these attributes that fall within the text selection + * (used to drive initial selection in the wizard) + */ + Pair<Map<String, List<Attr>>, Set<Attr>> getAvailableAttributes() { + mAvailableAttributes = new TreeMap<String, List<Attr>>(); + Set<Attr> withinSelection = new HashSet<Attr>(); + for (Element element : getElements()) { + IndexedRegion elementRegion = getRegion(element); + boolean allIncluded = + (mOriginalSelectionStart <= elementRegion.getStartOffset() && + mOriginalSelectionEnd >= elementRegion.getEndOffset()); + + NamedNodeMap attributeMap = element.getAttributes(); + for (int i = 0, n = attributeMap.getLength(); i < n; i++) { + Attr attribute = (Attr) attributeMap.item(i); + + String name = attribute.getLocalName(); + if (name == null || name.equals(ATTR_ID) || name.startsWith(ATTR_STYLE) + || name.startsWith(ATTR_LAYOUT_PREFIX) || name.equals(ATTR_TEXT) + || name.equals(ATTR_HINT) || name.equals(ATTR_SRC)) { + // Don't offer to extract attributes that don't make sense in + // styles (like "id" or "style"), or attributes that the user + // probably does not want to define in styles (like layout + // attributes such as layout_width, or the label of a button etc). + // This makes the options offered listed in the wizard simpler. + // In special cases where the user *does* want to set one of these + // attributes, they can always do it manually so optimize for + // the common case here. + continue; + } + + // Skip attributes that are in a namespace other than the Android one + String namespace = attribute.getNamespaceURI(); + if (namespace != null && !ANDROID_URI.equals(namespace)) { + continue; + } + + if (!allIncluded) { + IndexedRegion region = getRegion(attribute); + boolean attributeIncluded = mOriginalSelectionStart < region.getEndOffset() && + mOriginalSelectionEnd >= region.getStartOffset(); + if (attributeIncluded) { + withinSelection.add(attribute); + } + } else { + withinSelection.add(attribute); + } + + List<Attr> list = mAvailableAttributes.get(name); + if (list == null) { + list = new ArrayList<Attr>(); + mAvailableAttributes.put(name, list); + } + list.add(attribute); + } + } + + return Pair.of(mAvailableAttributes, withinSelection); + } + + IFile getStyleFile(IProject project) { + return project.getFile(new Path(FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP + + mStyleFileName)); + } + + @Override + protected List<Change> computeChanges() { + List<Change> changes = new ArrayList<Change>(); + if (mChosenAttributes.size() == 0) { + return changes; + } + + IFile file = getStyleFile(mEditor.getProject()); + boolean createFile = !file.exists(); + int insertAtIndex; + String initialIndent = null; + if (!createFile) { + Pair<Integer, String> context = computeInsertContext(file); + insertAtIndex = context.getFirst(); + initialIndent = context.getSecond(); + } else { + insertAtIndex = 0; + } + + TextFileChange addFile = new TextFileChange("Create new separate style declaration", file); + addFile.setTextType(EXT_XML); + changes.add(addFile); + String styleString = computeStyleDeclaration(createFile, initialIndent); + addFile.setEdit(new InsertEdit(insertAtIndex, styleString)); + + // Remove extracted attributes? + MultiTextEdit rootEdit = new MultiTextEdit(); + if (mRemoveExtracted || mRemoveAll) { + for (Attr attribute : mChosenAttributes) { + List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr attr : list) { + if (mRemoveAll || attr.getValue().equals(attribute.getValue())) { + removeAttribute(rootEdit, attr); + } + } + } + } + + // Set the style attribute? + if (mApplyStyle) { + for (Element element : getElements()) { + String value = ResourceResolver.PREFIX_RESOURCE_REF + + ResourceResolver.REFERENCE_STYLE + mStyleName; + setAttribute(rootEdit, element, null, null, ATTR_STYLE, value); + } + } + + if (rootEdit.hasChildren()) { + IFile sourceFile = mEditor.getInputFile(); + TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + } + + return changes; + } + + private String computeStyleDeclaration(boolean createFile, String initialIndent) { + StringBuilder sb = new StringBuilder(); + if (createFile) { + sb.append(NewXmlFileWizard.XML_HEADER_LINE); + sb.append('<').append(ROOT_ELEMENT).append(' '); + sb.append(XMLNS_COLON).append(ANDROID_NS_NAME).append('=').append('"'); + sb.append(ANDROID_URI); + sb.append('"').append('>').append('\n'); + } + + // Indent. Use the existing indent found for previous <style> elements in + // the resource file - but if that indent was 0 (e.g. <style> elements are + // at the left margin) only use it to indent the style elements and use a real + // nonzero indent for its children. + String indent = " "; //$NON-NLS-1$ + if (initialIndent == null) { + initialIndent = indent; + } else if (initialIndent.length() > 0) { + indent = initialIndent; + } + sb.append(initialIndent); + String styleTag = "style"; //$NON-NLS-1$ // TODO - use constant in parallel changeset + sb.append('<').append(styleTag).append(' ').append(NAME_ATTR).append('=').append('"'); + sb.append(mStyleName); + sb.append('"'); + if (mParent != null) { + sb.append(' ').append(PARENT_ATTR).append('=').append('"'); + sb.append(mParent); + sb.append('"'); + } + sb.append('>').append('\n'); + + for (Attr attribute : mChosenAttributes) { + sb.append(initialIndent).append(indent); + sb.append('<').append(ITEM_TAG).append(' ').append(NAME_ATTR).append('=').append('"'); + // We've already enforced that regardless of prefix, only attributes with + // an Android namespace can be in the set of chosen attributes. Rewrite the + // prefix to android here. + if (attribute.getPrefix() != null) { + sb.append(ANDROID_NS_NAME_PREFIX); + } + sb.append(attribute.getLocalName()); + sb.append('"').append('>'); + sb.append(attribute.getValue()); + sb.append('<').append('/').append(ITEM_TAG).append('>').append('\n'); + } + sb.append(initialIndent).append('<').append('/').append(styleTag).append('>').append('\n'); + + if (createFile) { + sb.append('<').append('/').append(ROOT_ELEMENT).append('>').append('\n'); + } + String styleString = sb.toString(); + return styleString; + } + + /** Computes the location in the file to insert the new style element at, as well as + * the exact indent string to use to indent the {@code <style>} element. + * @param file the styles.xml file to insert into + * @return a pair of an insert offset and an indent string + */ + private Pair<Integer, String> computeInsertContext(final IFile file) { + int insertAtIndex = -1; + // Find the insert of the final </resources> item where we will insert + // the new style elements. + String indent = null; + IModelManager modelManager = StructuredModelManager.getModelManager(); + IStructuredModel model = null; + try { + model = modelManager.getModelForRead(file); + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + IDOMDocument otherDocument = domModel.getDocument(); + Element root = otherDocument.getDocumentElement(); + Node lastChild = root.getLastChild(); + if (lastChild != null) { + if (lastChild instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) lastChild; + insertAtIndex = region.getStartOffset() + region.getLength(); + } + + // Compute indent + while (lastChild != null) { + if (lastChild.getNodeType() == Node.ELEMENT_NODE) { + IStructuredDocument document = model.getStructuredDocument(); + indent = AndroidXmlEditor.getIndent(document, lastChild); + break; + } + lastChild = lastChild.getPreviousSibling(); + } + } + } + } catch (IOException e) { + AdtPlugin.log(e, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + + if (insertAtIndex == -1) { + String contents = AdtPlugin.readFile(file); + insertAtIndex = contents.indexOf("</" + ROOT_ELEMENT + ">"); //$NON-NLS-1$ + if (insertAtIndex == -1) { + insertAtIndex = contents.length(); + } + } + + return Pair.of(insertAtIndex, indent); + } + + @Override + VisualRefactoringWizard createWizard() { + return new ExtractStyleWizard(this, mEditor); + } + + 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.extract.style", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ExtractStyleRefactoring(args); + } + } + + /** + * Determines the parent style to be used for this refactoring + * + * @return the parent style to be used for this refactoring + */ + public String getParentStyle() { + Set<String> styles = new HashSet<String>(); + for (Element element : getElements()) { + // Includes "" for elements not setting the style + styles.add(element.getAttribute(ATTR_STYLE)); + } + + if (styles.size() > 1) { + // The elements differ in what style attributes they are set to + return null; + } + + String style = styles.iterator().next(); + if (style != null && style.length() > 0) { + return style; + } + + // None of the elements set the style -- see if they have the same widget types + // and if so offer to extend the theme style for that widget type + + Set<String> types = new HashSet<String>(); + for (Element element : getElements()) { + types.add(element.getTagName()); + } + + if (types.size() == 1) { + String view = DescriptorsUtils.getBasename(types.iterator().next()); + + ResourceResolver resolver = mEditor.getGraphicalEditor().createResolver(); + // Look up the theme item name, which for a Button would be "buttonStyle", and so on. + String n = Character.toLowerCase(view.charAt(0)) + view.substring(1) + + "Style"; //$NON-NLS-1$ + ResourceValue value = resolver.findItemInTheme(n); + if (value != null) { + ResourceValue resolvedValue = resolver.resolveResValue(value); + String name = resolvedValue.getName(); + if (name != null) { + if (resolvedValue.isFramework()) { + return ResourceResolver.PREFIX_ANDROID + name; + } else { + return name; + } + } + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java new file mode 100644 index 0000000..2828288 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java @@ -0,0 +1,431 @@ +/* + * 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 org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER; +import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.w3c.dom.Attr; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +class ExtractStyleWizard extends VisualRefactoringWizard { + public ExtractStyleWizard(ExtractStyleRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle(ref.getName()); + } + + @Override + protected void addUserInputPages() { + String initialName = "styleName"; + addPage(new InputPage(mEditor.getProject(), initialName)); + } + + /** + * Wizard page which inputs parameters for the {@link ExtractStyleRefactoring} + * operation + */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final String mSuggestedName; + private Text mNameText; + private Table mTable; + private Button mRemoveExtracted; + private Button mSetStyle; + private Button mRemoveAll; + private Button mExtend;; + private CheckboxTableViewer mCheckedView; + + private String mParentStyle; + private Set<Attr> mInSelection; + private List<Attr> mAllAttributes; + private Map<Attr, Integer> mFrequencyCount; + private Set<Attr> mShown; + private List<Attr> mInitialChecked; + private List<Map.Entry<String, List<Attr>>> mRoot; + private Map<String, List<Attr>> mAvailableAttributes; + + public InputPage(IProject project, String suggestedName) { + super("ExtractStyleInputPage"); + mProject = project; + mSuggestedName = suggestedName; + } + + public void createControl(Composite parent) { + initialize(); + + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label nameLabel = new Label(composite, SWT.NONE); + nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + nameLabel.setText("Style Name:"); + + mNameText = new Text(composite, SWT.BORDER); + mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mNameText.addModifyListener(mModifyValidateListener); + + mRemoveExtracted = new Button(composite, SWT.CHECK); + mRemoveExtracted.setSelection(true); + mRemoveExtracted.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveExtracted.setText("Remove extracted attributes"); + mRemoveExtracted.addSelectionListener(mSelectionValidateListener); + + mRemoveAll = new Button(composite, SWT.CHECK); + mRemoveAll.setSelection(false); + mRemoveAll.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveAll.setText("Remove all extracted attributes regardless of value"); + mRemoveAll.addSelectionListener(mSelectionValidateListener); + + boolean defaultSetStyle = false; + if (mParentStyle != null) { + mExtend = new Button(composite, SWT.CHECK); + mExtend.setSelection(true); + mExtend.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mExtend.setText(String.format("Extend %1$s", mParentStyle)); + mExtend.addSelectionListener(mSelectionValidateListener); + defaultSetStyle = true; + } + + mSetStyle = new Button(composite, SWT.CHECK); + mSetStyle.setSelection(defaultSetStyle); + mSetStyle.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mSetStyle.setText("Set style attribute on extracted elements"); + mSetStyle.addSelectionListener(mSelectionValidateListener); + + new Label(composite, SWT.NONE); + new Label(composite, SWT.NONE); + + Label tableLabel = new Label(composite, SWT.NONE); + tableLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + tableLabel.setText("Choose style attributes to extract:"); + + mCheckedView = CheckboxTableViewer.newCheckList(composite, SWT.BORDER + | SWT.FULL_SELECTION | SWT.HIDE_SELECTION); + mTable = mCheckedView.getTable(); + mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 2)); + ((GridData) mTable.getLayoutData()).heightHint = 200; + + Object[] children = mAllAttributes.toArray(); + + mCheckedView.setContentProvider(new ArgumentContentProvider(mRoot, children)); + mCheckedView.setLabelProvider(new ArgumentLabelProvider(mFrequencyCount)); + mCheckedView.setInput(mRoot); + final Object[] initialSelection = mInitialChecked.toArray(); + mCheckedView.setCheckedElements(initialSelection); + + mCheckedView.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + // Try to disable other elements that conflict with this + boolean isChecked = event.getChecked(); + if (isChecked) { + Attr attribute = (Attr) event.getElement(); + List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr other : list) { + if (other != attribute && mShown.contains(other)) { + mCheckedView.setChecked(other, false); + } + } + } + + validatePage(); + } + }); + + // Select All / Deselect All + Composite buttonForm = new Composite(composite, SWT.NONE); + buttonForm.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); + rowLayout.marginTop = 0; + rowLayout.marginLeft = 0; + buttonForm.setLayout(rowLayout); + Button checkAllButton = new Button(buttonForm, SWT.FLAT); + checkAllButton.setText("Select All"); + checkAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // Select "all" (but not conflicting settings) + mCheckedView.setCheckedElements(initialSelection); + } + }); + Button uncheckAllButton = new Button(buttonForm, SWT.FLAT); + uncheckAllButton.setText("Deselect All"); + uncheckAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mCheckedView.setAllChecked(false); + } + }); + + // Initialize UI: + if (mSuggestedName != null) { + mNameText.setText(mSuggestedName); + } + + setControl(composite); + validatePage(); + } + + private void initialize() { + ExtractStyleRefactoring ref = (ExtractStyleRefactoring) getRefactoring(); + + mParentStyle = ref.getParentStyle(); + + // Set up data structures needed by the wizard -- to compute the actual + // attributes to list in the wizard (there could be multiple attributes + // of the same name (on different elements) and we only want to show one, etc.) + + Pair<Map<String, List<Attr>>, Set<Attr>> result = ref.getAvailableAttributes(); + // List of all available attributes on the selected elements + mAvailableAttributes = result.getFirst(); + // Set of attributes that overlap the text selection, or all attributes if + // wizard is invoked from GUI context + mInSelection = result.getSecond(); + + // The root data structure, which we set as the table root. The content provider + // will produce children from it. This is the entry set of a map from + // attribute name to list of attribute nodes for that attribute name. + mRoot = new ArrayList<Map.Entry<String, List<Attr>>>( + mAvailableAttributes.entrySet()); + + // Sort the items by attribute name -- the attribute name is the key + // in the entry set above. + Collections.sort(mRoot, new Comparator<Map.Entry<String, List<Attr>>>() { + public int compare(Map.Entry<String, List<Attr>> e1, + Map.Entry<String, List<Attr>> e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + // Set of attributes actually included in the list shown to the user. + // (There could be many additional "aliasing" nodes on other elements + // with the same name.) Note however that we DO show multiple attribute + // occurrences of the same attribute name: precisely one for each unique -value- + // of that attribute. + mShown = new HashSet<Attr>(); + + // The list of initially checked attributes. + mInitialChecked = new ArrayList<Attr>(); + + // All attributes. + mAllAttributes = new ArrayList<Attr>(); + + // Frequency count, from attribute to integer. Attributes that do not + // appear in the list have frequency 1, not 0. + mFrequencyCount = new HashMap<Attr, Integer>(); + + for (Map.Entry<String, List<Attr>> entry : mRoot) { + // Iterate over all attributes of the same name, and sort them + // by value. This will make it easy to list each -unique- value in the + // wizard. + List<Attr> attrList = entry.getValue(); + Collections.sort(attrList, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + return a1.getValue().compareTo(a2.getValue()); + } + }); + + // We need to compute a couple of things: the frequency for all identical + // values (and stash them in the frequency map), and record the first + // attribute with a particular value into the list of attributes to + // be shown. + Attr prevAttr = null; + String prev = null; + List<Attr> uniqueValueAttrs = new ArrayList<Attr>(); + for (Attr attr : attrList) { + String value = attr.getValue(); + if (value.equals(prev)) { + Integer count = mFrequencyCount.get(prevAttr); + if (count == null) { + count = Integer.valueOf(2); + } else { + count = Integer.valueOf(count.intValue() + 1); + } + mFrequencyCount.put(prevAttr, count); + } else { + uniqueValueAttrs.add(attr); + prev = value; + prevAttr = attr; + } + } + + // Sort the values by frequency (and for equal frequencies, alphabetically + // by value) + Collections.sort(uniqueValueAttrs, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + Integer f1 = mFrequencyCount.get(a1); + Integer f2 = mFrequencyCount.get(a2); + if (f1 == null) { + f1 = Integer.valueOf(1); + } + if (f2 == null) { + f2 = Integer.valueOf(1); + } + int delta = f2.intValue() - f1.intValue(); + if (delta != 0) { + return delta; + } else { + return a1.getValue().compareTo(a2.getValue()); + } + } + }); + + // Add the items in order, and select those attributes that overlap + // the selection + mAllAttributes.addAll(uniqueValueAttrs); + mShown.addAll(uniqueValueAttrs); + Attr first = uniqueValueAttrs.get(0); + if (mInSelection.contains(first)) { + mInitialChecked.add(first); + } + } + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + String text = mNameText.getText().trim(); + + if (text.length() == 0) { + setErrorMessage("Provide a name for the new style"); + ok = false; + } else { + ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, + ResourceType.STYLE); + String message = validator.isValid(text); + if (message != null) { + setErrorMessage(message); + ok = false; + } + } + + Object[] checkedElements = mCheckedView.getCheckedElements(); + if (checkedElements.length == 0) { + setErrorMessage("Choose at least one attribute to extract"); + ok = false; + } + + if (ok) { + setErrorMessage(null); + + // Record state + ExtractStyleRefactoring refactoring = (ExtractStyleRefactoring) getRefactoring(); + refactoring.setStyleName(text); + refactoring.setRemoveExtracted(mRemoveExtracted.getSelection()); + refactoring.setRemoveAll(mRemoveAll.getSelection()); + refactoring.setApplyStyle(mSetStyle.getSelection()); + if (mExtend != null && mExtend.getSelection()) { + refactoring.setParent(mParentStyle); + } + List<Attr> attributes = new ArrayList<Attr>(); + for (Object o : checkedElements) { + attributes.add((Attr) o); + } + refactoring.setChosenAttributes(attributes); + } + + setPageComplete(ok); + return ok; + } + } + + private static class ArgumentLabelProvider extends StyledCellLabelProvider { + public ArgumentLabelProvider(Map<Attr, Integer> frequencyCount) { + mFrequencyCount = frequencyCount; + } + + private Map<Attr, Integer> mFrequencyCount = + new HashMap<Attr, Integer>(); + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + Attr attribute = (Attr) element; + + StyledString styledString = new StyledString(); + styledString.append(attribute.getLocalName()); + styledString.append(" = ", QUALIFIER_STYLER); + styledString.append(attribute.getValue()); + + Integer f = mFrequencyCount.get(attribute); + if (f != null) { + styledString.append(String.format(" (%d)", f.intValue()), DECORATIONS_STYLER); + } + cell.setText(styledString.toString()); + cell.setStyleRanges(styledString.getStyleRanges()); + super.update(cell); + } + } + + private static class ArgumentContentProvider implements IStructuredContentProvider { + private Object[] mChildren; + private List<Entry<String, List<Attr>>> mRoot; + + public ArgumentContentProvider(List<Entry<String, List<Attr>>> root, Object[] children) { + mRoot = root; + mChildren = children; + } + + public Object[] getElements(Object inputElement) { + if (inputElement == mRoot) { + return mChildren; + } + + return new Object[0]; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java index 2de8536..4baad1d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java @@ -81,6 +81,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor { boolean isValue = false; boolean isTagName = false; + boolean isAttributeName = false; IStructuredModel model = null; try { model = xmlEditor.getModelForRead(); @@ -100,6 +101,9 @@ public class RefactoringAssistant implements IQuickAssistProcessor { || type.equals(DOMRegionContext.XML_TAG_OPEN) || type.equals(DOMRegionContext.XML_TAG_CLOSE)) { isTagName = true; + } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) + || type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { + isAttributeName = true; } } } finally { @@ -108,7 +112,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor { } } - if (isValue || isTagName) { + if (isValue || isTagName || isAttributeName) { StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor(); ISelectionProvider provider = structuredEditor.getSelectionProvider(); ISelection selection = provider.getSelection(); @@ -117,14 +121,41 @@ public class RefactoringAssistant implements IQuickAssistProcessor { // These operations currently do not work on ranges if (textSelection.getLength() > 0) { + // ...except for Extract Style where the actual attributes overlapping + // the selection is going to be the set of eligible attributes + if (isAttributeName && xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)) + }; + } return null; } - if (isValue) { + if (isAttributeName && xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; return new ICompletionProposal[] { + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)), + }; + } else if (isValue) { + if (xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(xmlEditor, + new ExtractStringRefactoring(file, xmlEditor, + textSelection)), + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, + textSelection, null)), + }; + } else { + return new ICompletionProposal[] { new RefactoringProposal(xmlEditor, new ExtractStringRefactoring(file, xmlEditor, textSelection)) - }; + }; + } } else if (xmlEditor instanceof LayoutEditor) { LayoutEditor editor = (LayoutEditor) xmlEditor; return new ICompletionProposal[] { @@ -135,6 +166,8 @@ public class RefactoringAssistant implements IQuickAssistProcessor { new RefactoringProposal(editor, new ChangeLayoutRefactoring(file, editor, textSelection, null)), new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)), + new RefactoringProposal(editor, new ExtractIncludeRefactoring(file, editor, textSelection, null)), }; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java index 4b2a0cb..7db0a5c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java @@ -113,6 +113,10 @@ public abstract class VisualRefactoring extends Refactoring { protected final List<Element> mElements; protected final ITreeSelection mTreeSelection; protected final ITextSelection mSelection; + /** Same as {@link #mSelectionStart} but not adjusted to element edges */ + protected int mOriginalSelectionStart = -1; + /** Same as {@link #mSelectionEnd} but not adjusted to element edges */ + protected int mOriginalSelectionEnd = -1; protected final Map<Element, String> mGeneratedIdMap = new HashMap<Element, String>(); protected final Set<String> mGeneratedIds = new HashSet<String>(); @@ -132,6 +136,8 @@ public abstract class VisualRefactoring extends Refactoring { mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path); mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START)); mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END)); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; mEditor = null; mElements = null; mSelection = null; @@ -147,6 +153,8 @@ public abstract class VisualRefactoring extends Refactoring { mProject = editor != null ? editor.getProject() : null; mSelectionStart = 0; mSelectionEnd = 0; + mOriginalSelectionStart = 0; + mOriginalSelectionEnd = 0; mSelection = null; mTreeSelection = null; @@ -162,6 +170,8 @@ public abstract class VisualRefactoring extends Refactoring { if (start >= 0) { mSelectionStart = start; mSelectionEnd = end; + mOriginalSelectionStart = start; + mOriginalSelectionEnd = end; } } @@ -199,11 +209,19 @@ public abstract class VisualRefactoring extends Refactoring { if (start >= 0) { mSelectionStart = start; mSelectionEnd = end; + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; + } + if (selection != null) { + mOriginalSelectionStart = selection.getOffset(); + mOriginalSelectionEnd = mOriginalSelectionStart + selection.getLength(); } } else if (selection != null) { // TODO: update selection to boundaries! mSelectionStart = selection.getOffset(); mSelectionEnd = mSelectionStart + selection.getLength(); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; } mElements = initElements(); @@ -663,6 +681,8 @@ public abstract class VisualRefactoring extends Refactoring { } else if (mSelection != null) { mSelectionStart = mSelection.getOffset(); mSelectionEnd = mSelectionStart + mSelection.getLength(); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; // Figure out the range of selected nodes from the document offsets IStructuredDocument doc = mEditor.getStructuredDocument(); @@ -919,7 +939,11 @@ public abstract class VisualRefactoring extends Refactoring { private void addAttributeDeclaration(MultiTextEdit rootEdit, int offset, String attributePrefix, String attributeName, String attributeValue) { StringBuilder sb = new StringBuilder(); - sb.append(' ').append(attributePrefix).append(':'); + sb.append(' '); + + if (attributePrefix != null) { + sb.append(attributePrefix).append(':'); + } sb.append(attributeName).append('=').append('"'); sb.append(attributeValue).append('"'); @@ -942,7 +966,8 @@ public abstract class VisualRefactoring extends Refactoring { int valueStart = -1; boolean useNextValue = false; - String targetName = attributePrefix + ':' + attributeName; + String targetName = attributePrefix != null + ? attributePrefix + ':' + attributeName : attributeName; // Look at all attribute values and look for an id reference match for (int j = 0; j < region.getNumberOfRegions(); j++) { @@ -984,16 +1009,22 @@ public abstract class VisualRefactoring extends Refactoring { String attributeName) { if (element.hasAttributeNS(uri, attributeName)) { Attr attribute = element.getAttributeNodeNS(uri, attributeName); - IndexedRegion region = getRegion(attribute); - if (region != null) { - int startOffset = region.getStartOffset(); - int endOffset = region.getEndOffset(); - DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); - rootEdit.addChild(deletion); - } + removeAttribute(rootEdit, attribute); + } + } + + /** Strips out the given attribute, if defined */ + protected void removeAttribute(MultiTextEdit rootEdit, Attr attribute) { + IndexedRegion region = getRegion(attribute); + if (region != null) { + int startOffset = region.getStartOffset(); + int endOffset = region.getEndOffset(); + DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); + rootEdit.addChild(deletion); } } + /** * Removes the given element's opening and closing tags (including all of its * attributes) but leaves any children alone diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java index 8ff6b6e..6a2368a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java @@ -42,6 +42,7 @@ public final class ResourcesDescriptors implements IDescriptorProvider { public static final String ITEM_TAG = "item"; //$NON-NLS-1$ public static final String NAME_ATTR = "name"; //$NON-NLS-1$ public static final String TYPE_ATTR = "type"; //$NON-NLS-1$ + public static final String PARENT_ATTR = "parent"; //$NON-NLS-1$ private static final ResourcesDescriptors sThis = new ResourcesDescriptors(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java index 9c0d31c..a5a24d7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java @@ -52,6 +52,7 @@ import java.io.UnsupportedEncodingException; * the resource folder, resource type and file name. It then creates the XML file. */ public class NewXmlFileWizard extends Wizard implements INewWizard { + public static final String XML_HEADER_LINE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$ @@ -111,11 +112,10 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { return false; } else { IFile file = created.getFirst(); - IRegion region = created.getSecond(); - // Open the file in an editor + // Open the file try { - AdtPlugin.openFile(file, region); + AdtPlugin.openFile(file, null, false /* showEditorTab */); } catch (PartInitException e) { AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ file.getFullPath().toString()); @@ -166,7 +166,7 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { createWsParentDirectory(file.getParent()); } - StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(XML_HEADER_LINE); sb.append('<').append(root); if (xmlns != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java index 2832c1d..07c0fe1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java @@ -178,7 +178,8 @@ public class AaptQuickFixTest extends AdtProjectTest { ((FileEditorInput) currentFile.getEditorInput()).getFile().getProjectRelativePath()); // Look up caret offset - assertTrue(currentFile instanceof AndroidXmlEditor); + assertTrue(currentFile != null ? currentFile.getClass().getName() : "null", + currentFile instanceof AndroidXmlEditor); AndroidXmlEditor newEditor = (AndroidXmlEditor) currentFile; ISourceViewer newViewer = newEditor.getStructuredSourceViewer(); Point selectedRange = newViewer.getSelectedRange(); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java index f987729..db74295 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java @@ -299,7 +299,6 @@ public class AdtProjectTest extends SdkTestCase { * (such as code completion apply-tests) */ protected String getDiff(String before, String after) { - // Do line by line analysis String[] beforeLines = before.split("\n"); String[] afterLines = after.split("\n"); @@ -324,18 +323,44 @@ public class AdtProjectTest extends SdkTestCase { } } + + boolean showBeforeWindow = firstDelta >= beforeLines.length - lastDelta; + boolean showAfterWindow = firstDelta >= afterLines.length - lastDelta; + StringBuilder sb = new StringBuilder(); + if (showAfterWindow && firstDelta > 0) { + sb.append(" "); + sb.append(afterLines[firstDelta - 1]); + sb.append('\n'); + } for (int i = firstDelta; i < beforeLines.length - lastDelta; i++) { sb.append("< "); sb.append(beforeLines[i]); sb.append('\n'); } + if (showAfterWindow && lastDelta < afterLines.length - 1) { + sb.append(" "); + sb.append(afterLines[afterLines.length - (lastDelta -1)]); + sb.append('\n'); + } + sb.append("---\n"); + + if (showBeforeWindow && firstDelta > 0) { + sb.append(" "); + sb.append(beforeLines[firstDelta - 1]); + sb.append('\n'); + } for (int i = firstDelta; i < afterLines.length - lastDelta; i++) { sb.append("> "); sb.append(afterLines[i]); sb.append('\n'); } + if (showBeforeWindow && lastDelta < beforeLines.length - 1) { + sb.append(" "); + sb.append(beforeLines[beforeLines.length - (lastDelta -1)]); + sb.append('\n'); + } return sb.toString(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java new file mode 100644 index 0000000..a7bfbad --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java @@ -0,0 +1,229 @@ +/* + * 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 com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ExtractStyleRefactoringTest extends RefactoringTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + return true; + } + + public void testExtract1() throws Exception { + // Test extracting into a new style file + checkRefactoring("extractstyle1.xml", "newstyles.xml", "newstyle", + false /* removeExtracted */, false /* applyStyle */, null, 1, "@+id/button2"); + } + + public void testExtract1b() throws Exception { + // Extract and apply new style + checkRefactoring("extractstyle1.xml", "newstyles2.xml", "newstyle", + false /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract1c() throws Exception { + // Extract and remove extracted + checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", + true /* removeExtracted */, false /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract1d() throws Exception { + // Extract and apply style and remove extracted + checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract2() throws Exception { + getTestDataFile(getProject(), "navigationstyles.xml", "res/values/navigationstyles.xml"); + + // -Modify- the existing styles.xml file + checkRefactoring("extractstyle1.xml", "navigationstyles.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract3() throws Exception { + // Select multiple elements - overlap in values. + checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "@+id/button1", "@+id/button2"); + } + + // This test fails for some reason - not in the refactoring (checked manually) + // but the DOM model returns null when run in a test context. + public void testExtract4() throws Exception { + // Test extracting on a single caret position over an attribute: Should extract + // just that one attribute + checkRefactoringByOffset("extractstyle1.xml", "newstyles5.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "android:text^Color=\"#FF00FF\"", "android:text^Color=\"#FF00FF\""); + } + + public void testExtract5() throws Exception { + // Test extracting on a range selection inside an element: should extract just + // the attributes that overlap the selection + checkRefactoringByOffset("extractstyle1.xml", "newstyles6.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "android:^textSize=\"20pt", + "android:id=\"@+id/button1\" android:layout_a^lignParentBottom"); + } + + public void testExtract6() throws Exception { + // Test extracting on a single caret position which is not over any attributes: + checkRefactoringByOffset("extractstyle1.xml", "newstyles7.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 0, + "<Bu^tton", "<Bu^tton"); + } + + public void testExtract7() throws Exception { + // Verify that even with a different namespace prefix we end up with android: + // in the extracted style + checkRefactoring("extractstyle2.xml", "newstyles8.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "@+id/button1", "@+id/button2"); + } + + public void testExtract8() throws Exception { + // Test setting parent style + checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", + true /* removeExtracted */, false /* applyStyle */, "android:Widget.Button", + 2, "@+id/button2"); + } + + // Check extract style on a selection of elements + private void checkRefactoring(String basename, String styleFileName, String newStyleName, + boolean removeExtracted, boolean applyStyle, String parentStyle, + int expectedModifiedFileCount, String... ids) throws Exception { + assertTrue(ids.length > 0); + + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + List<Element> selectedElements = getElements(info.mElement, ids); + + // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities + // will succeed + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); + IDE.openEditor(page, file); + + ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(selectedElements, + layoutEditor); + checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, + parentStyle, expectedModifiedFileCount, file, refactoring); + } + + // Check extract style against a set of editor text locations + private void checkRefactoringByOffset(String basename, String styleFileName, + String newStyleName, boolean removeExtracted, boolean applyStyle, + String parentStyle, + int expectedModifiedFileCount, String beginCaretLocation, String endCaretLocation) + throws Exception { + IFile file = getLayoutFile(getProject(), basename); + int beginOffset = getCaretOffset(file, beginCaretLocation); + int endOffset = getCaretOffset(file, endCaretLocation); + + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + + // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities + // will succeed + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); + IDE.openEditor(page, file); + + ITextSelection selection = new TextSelection(beginOffset, endOffset - beginOffset); + ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(file, + layoutEditor, selection, null); + checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, + parentStyle, expectedModifiedFileCount, file, refactoring); + } + + // Common test code used by the other two check methods + private void checkRefactoring(String basename, String styleFileName, String newStyleName, + boolean removeExtracted, boolean applyStyle, String parentStyle, + int expectedModifiedFileCount, IFile file, + ExtractStyleRefactoring refactoring) throws Exception { + refactoring.setStyleName(newStyleName); + refactoring.setApplyStyle(applyStyle); + refactoring.setRemoveExtracted(removeExtracted); + refactoring.setStyleFileName(styleFileName); + refactoring.setParent(parentStyle); + + // Pick the attributes to extract -- for now everything (and where there are + // conflicting values, pick the first one) + Pair<Map<String, List<Attr>>, Set<Attr>> result = refactoring.getAvailableAttributes(); + Map<String, List<Attr>> availableAttributes = result.getFirst(); + Set<Attr> selected = result.getSecond(); + List<Attr> chosenAttributes = new ArrayList<Attr>(); + for (List<Attr> list : availableAttributes.values()) { + Collections.sort(list, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + return a1.getValue().compareTo(a2.getValue()); + } + }); + Attr attr = list.get(0); + if (selected.contains(attr)) { + chosenAttributes.add(attr); + } + } + refactoring.setChosenAttributes(chosenAttributes); + + List<Change> changes = refactoring.computeChanges(); + assertEquals(expectedModifiedFileCount, changes.size()); + + Map<IPath,String> fileToGolden = new HashMap<IPath,String>(); + IPath sourcePath = file.getProjectRelativePath(); + fileToGolden.put(sourcePath, basename); + IPath newPath = refactoring.getStyleFile(getProject()).getProjectRelativePath(); + fileToGolden.put(newPath, styleFileName); + + checkEdits(changes, fileToGolden, true); + + int modifiedFileCount = 0; + for (Change change : changes) { + if (change instanceof TextFileChange) { + modifiedFileCount++; + } + } + assertEquals(expectedModifiedFileCount, modifiedFileCount); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java index 773c43c..498f65a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java @@ -71,7 +71,7 @@ public class RefactoringAssistantTest extends AdtProjectTest { final int offset = caretContextIndex + caretDelta; - RefactoringAssistant aaptQuickFix = new RefactoringAssistant(); + RefactoringAssistant refactoringAssistant = new RefactoringAssistant(); // Open file IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); @@ -94,7 +94,7 @@ public class RefactoringAssistantTest extends AdtProjectTest { return viewer; } }; - ICompletionProposal[] proposals = aaptQuickFix + ICompletionProposal[] proposals = refactoringAssistant .computeQuickAssistProposals(invocationContext); if (proposals != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java index 947840c..661b553 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.refactoring; import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; @@ -107,8 +108,12 @@ public class RefactoringTest extends AdtProjectTest { } protected void checkEdits(List<Change> changes, - Map<IPath, String> fileToGoldenName) throws BadLocationException, - IOException { + Map<IPath, String> fileToGoldenName) throws BadLocationException { + checkEdits(changes, fileToGoldenName, false); + } + + protected void checkEdits(List<Change> changes, + Map<IPath, String> fileToGoldenName, boolean createDiffs) throws BadLocationException { for (Change change : changes) { if (change instanceof TextFileChange) { TextFileChange tf = (TextFileChange) change; @@ -125,6 +130,8 @@ public class RefactoringTest extends AdtProjectTest { IDocument document = new Document(); document.set(xml); + String before = document.get(); + TextEdit edit = tf.getEdit(); if (edit instanceof MultiTextEdit) { MultiTextEdit edits = (MultiTextEdit) edit; @@ -134,6 +141,17 @@ public class RefactoringTest extends AdtProjectTest { } String actual = document.get(); + + if (createDiffs) { + // Use a diff as the golden file instead of the after + actual = getDiff(before, actual); + if (goldenName.endsWith(DOT_XML)) { + goldenName = goldenName.substring(0, + goldenName.length() - DOT_XML.length()) + + ".diff"; + } + } + assertEqualsGolden(goldenName, actual); } else { System.out.println("Ignoring non-textfilechange in refactoring result"); @@ -240,7 +258,7 @@ public class RefactoringTest extends AdtProjectTest { UiViewElementNode model = createModel(null, element); ViewInfo info = createInfos(model, relativePath); - CanvasViewInfo rootView = CanvasViewInfo.create(info).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(info, true /* layoutlib5 */).getFirst(); TestLayoutEditor layoutEditor = new TestLayoutEditor(file, structuredDocument, null); TestContext testInfo = createTestContext(); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff new file mode 100644 index 0000000..d734ccf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff @@ -0,0 +1,3 @@ +< <Button android:text="Button" +--- +> <Button style="@style/newstyle" android:text="Button" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff new file mode 100644 index 0000000..2ebc91d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff @@ -0,0 +1,4 @@ + android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" + </FrameLayout> +--- diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff new file mode 100644 index 0000000..ec560b3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff new file mode 100644 index 0000000..ec560b3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff new file mode 100644 index 0000000..f7fd22b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff @@ -0,0 +1,15 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +< android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="wrap_content" +> android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" +> android:textColor="#FF00FF" android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff new file mode 100644 index 0000000..a8e2af4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff @@ -0,0 +1,7 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" +> android:textSize="20pt" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff new file mode 100644 index 0000000..bcaff2a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff @@ -0,0 +1,8 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="wrap_content" +> android:textColor="#FF0000" android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff new file mode 100644 index 0000000..1db5e38 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:id="@+id/button1" ></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff new file mode 100644 index 0000000..2ebc91d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff @@ -0,0 +1,4 @@ + android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" + </FrameLayout> +--- diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info new file mode 100644 index 0000000..69f7739 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info @@ -0,0 +1,3 @@ +android.widget.LinearLayout [0,36,140,320] <LinearLayout> + android.widget.Button [0,0,140,62] <Button> + android.widget.Button [0,62,140,284] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml new file mode 100644 index 0000000..64c49b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml @@ -0,0 +1,11 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="match_parent"> + <Button android:text="Button" + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:textColor="#FF0000" android:textSize="20pt" + android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> + <Button android:text="Button" + android:layout_width="wrap_content" android:layout_height="fill_parent" + android:textColor="#FF00FF" android:textSize="20pt" + android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> +</FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff new file mode 100644 index 0000000..84c2ad7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff @@ -0,0 +1,13 @@ +< <Button foo:text="Button" +< foo:layout_width="wrap_content" foo:layout_height="wrap_content" +< foo:textColor="#FF0000" foo:textSize="20pt" +< foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> +< <Button foo:text="Button" +< foo:layout_width="wrap_content" foo:layout_height="fill_parent" +< foo:textColor="#00FF00" foo:textSize="20pt" +--- +> <Button style="@style/newstyle" foo:text="Button" +> foo:layout_width="wrap_content" foo:layout_height="wrap_content" +> foo:textColor="#FF0000" foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> +> <Button style="@style/newstyle" foo:text="Button" +> foo:layout_width="wrap_content" foo:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info new file mode 100644 index 0000000..69f7739 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info @@ -0,0 +1,3 @@ +android.widget.LinearLayout [0,36,140,320] <LinearLayout> + android.widget.Button [0,0,140,62] <Button> + android.widget.Button [0,62,140,284] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml new file mode 100644 index 0000000..3cb966f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml @@ -0,0 +1,11 @@ +<LinearLayout xmlns:foo="http://schemas.android.com/apk/res/android" + foo:layout_width="wrap_content" foo:layout_height="match_parent" foo:orientation="vertical"> + <Button foo:text="Button" + foo:layout_width="wrap_content" foo:layout_height="wrap_content" + foo:textColor="#FF0000" foo:textSize="20pt" + foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> + <Button foo:text="Button" + foo:layout_width="wrap_content" foo:layout_height="fill_parent" + foo:textColor="#00FF00" foo:textSize="20pt" + foo:id="@+id/button2" foo:layout_alignParentBottom="true"></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff new file mode 100644 index 0000000..141180b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff @@ -0,0 +1,6 @@ +--- + </style> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> + </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff new file mode 100644 index 0000000..3b4d930 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle" parent="android:Widget.Button"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff new file mode 100644 index 0000000..0685d94 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF0000</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff new file mode 100644 index 0000000..f052485 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff @@ -0,0 +1,8 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff new file mode 100644 index 0000000..ce1d4aa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff @@ -0,0 +1,8 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff new file mode 100644 index 0000000..51f0812 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff @@ -0,0 +1,13 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:layout_alignParentBottom">true</item> +> <item name="android:layout_height">wrap_content</item> +> <item name="android:layout_width">wrap_content</item> +> <item name="android:text">Button</item> +> <item name="android:textColor">#FF0000</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff new file mode 100644 index 0000000..8f7ad98 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#00FF00</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt index 853dbaa..457239f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt @@ -1,2 +1,3 @@ Quick assistant in sample1a.xml for <Button android:text="Fir^stButton": Extract Android String : Initiates the given refactoring operation +Extract Style : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt index dfa9d15..95187d3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt @@ -2,4 +2,5 @@ Quick assistant in sample1a.xml for <Bu^tton android:text: Wrap in Container : Initiates the given refactoring operation Change Widget Type : Initiates the given refactoring operation Change Layout : Initiates the given refactoring operation +Extract Style : Initiates the given refactoring operation Extract as Include : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt index 55bbbad..8123be4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt @@ -1,2 +1,2 @@ Quick assistant in sample1a.xml for <Button andr^oid:text="FirstButton": -None found. +Extract Style : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java index 494346a..d336b35 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java @@ -18,7 +18,9 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.rendering.api.MergeCookie; +import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.SessionParams.AdapterItemReference; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; @@ -65,6 +67,15 @@ public class CanvasViewInfoTest extends TestCase { } public void testNormalCreate() throws Exception { + normal(true); + } + + public void testNormalCreateLayoutLib5() throws Exception { + normal(false); + } + + private void normal(boolean layoutlib5) { + // Normal view hierarchy, no null keys anywhere UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); @@ -75,7 +86,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child2 = new ViewInfo("Button", child2Node, 0, 20, 70, 25); root.setChildren(Arrays.asList(child1, child2)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -100,6 +111,15 @@ public class CanvasViewInfoTest extends TestCase { } public void testShowIn() throws Exception { + showIn(false); + } + + public void testShowInLayoutLib5() throws Exception { + showIn(true); + } + + public void showIn(boolean layoutlib5) throws Exception { + // Test rendering of "Show Included In" (included content rendered // within an outer content that has null keys) @@ -112,7 +132,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", child21Node, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -137,6 +157,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeTag() throws Exception { + boolean layoutlib5 = true; + // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag) UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); @@ -149,7 +171,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -176,9 +198,10 @@ public class CanvasViewInfoTest extends TestCase { } public void testNoIncludeTag() throws Exception { + boolean layoutlib5 = false; + // Test rendering of included views on layoutlib 4- (e.g. no <include> tag cookie - // in - // view info) + // in view info) UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100); @@ -190,7 +213,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -217,6 +240,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeMatching() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -232,7 +257,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -272,6 +297,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMerge() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -286,7 +313,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -313,6 +340,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testInsertMerge() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -320,7 +349,7 @@ public class CanvasViewInfoTest extends TestCase { UiViewElementNode rootNode = createNode(mergeNode, "android.widget.Button", false); ViewInfo root = new ViewInfo("Button", rootNode, 10, 10, 100, 100); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("merge", rootView.getName()); assertSame(rootView.getUiViewNode(), mergeNode); @@ -340,6 +369,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testUnmatchedMissing() throws Exception { + boolean layoutlib5 = false; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); List<ViewInfo> children = new ArrayList<ViewInfo>(); @@ -387,7 +418,7 @@ public class CanvasViewInfoTest extends TestCase { } root.setChildren(children); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); // dump(root, 0); @@ -412,6 +443,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeCookies() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); @@ -431,7 +464,7 @@ public class CanvasViewInfoTest extends TestCase { } root.setChildren(children); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); @@ -446,6 +479,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeCookies2() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); @@ -460,12 +495,13 @@ public class CanvasViewInfoTest extends TestCase { ArrayList<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < 10; i++) { Object cookie = (i % 2) == 0 ? cookie1 : cookie2; - ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20); + ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, + (i + 1) * 20); children.add(childView); } root.setChildren(children); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNull(bounds); @@ -495,6 +531,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeBounds() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100); @@ -509,12 +547,13 @@ public class CanvasViewInfoTest extends TestCase { ArrayList<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < 10; i++) { Object cookie = (i % 2) == 0 ? cookie1 : cookie2; - ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20); + ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, + (i + 1) * 20); children.add(childView); } root.setChildren(children); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNotNull(rootView); @@ -548,21 +587,27 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeBounds2() throws Exception { + includeBounds2(false); + } + + public void testIncludeBounds2LayoutLib5() throws Exception { + includeBounds2(true); + } + + public void includeBounds2(boolean layoutlib5) throws Exception { + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100); UiViewElementNode node1 = createNode(rootNode, "childNode1", false); UiViewElementNode node2 = createNode(rootNode, "childNode2", false); - // Sets alternating merge cookies and checks whether the node sibling lists are - // okay and merged correctly - ViewInfo childView1 = new ViewInfo("childView1", node1, 0, 20, 50, 40); ViewInfo childView2 = new ViewInfo("childView2", node2, 0, 40, 50, 60); root.setChildren(Arrays.asList(childView1, childView2)); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNotNull(rootView); @@ -580,6 +625,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testGestureOverlayView() throws Exception { + boolean layoutlib5 = true; + // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag) UiViewElementNode rootNode = createNode("android.gesture.GestureOverlayView", true); @@ -590,7 +637,7 @@ public class CanvasViewInfoTest extends TestCase { root.setChildren(Collections.singletonList(child)); ViewInfo grandChild = new ViewInfo("Button", grandChildNode, 0, 20, 70, 25); child.setChildren(Collections.singletonList(grandChild)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("GestureOverlayView", rootView.getName()); @@ -611,6 +658,46 @@ public class CanvasViewInfoTest extends TestCase { assertFalse(grandChildView.isRoot()); } + public void testListView() throws Exception { + // For ListViews we get AdapterItemReferences as cookies. Ensure that this + // works properly. + // + // android.widget.FrameLayout [0,50,320,480] <FrameLayout> + // android.widget.ListView [0,0,320,430] <ListView> + // android.widget.LinearLayout [0,0,320,17] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // android.widget.LinearLayout [0,18,320,35] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // android.widget.LinearLayout [0,36,320,53] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // ... + + UiViewElementNode rootNode = createNode("FrameLayout", true); + UiViewElementNode childNode = createNode(rootNode, "ListView", false); + /*UiViewElementNode grandChildNode =*/ createNode(childNode, "LinearLayout", false); + /*UiViewElementNode greatGrandChildNode =*/ createNode(childNode, "TextView", false); + AdapterItemReference adapterItem = new SessionParams.AdapterItemReference("foo"); + + ViewInfo root = new ViewInfo("FrameLayout", rootNode, 0, 50, 320, 480); + ViewInfo child = new ViewInfo("ListView", childNode, 0, 0, 320, 430); + root.setChildren(Collections.singletonList(child)); + ViewInfo grandChild = new ViewInfo("LinearLayout", adapterItem, 0, 0, 320, 17); + child.setChildren(Collections.singletonList(grandChild)); + ViewInfo greatGrandChild = new ViewInfo("Button", null, 0, 0, 73, 17); + grandChild.setChildren(Collections.singletonList(greatGrandChild)); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /*layoutlib5*/).getFirst(); + assertNotNull(rootView); + + assertEquals("FrameLayout", rootView.getName()); + assertEquals(1, rootView.getChildren().size()); + assertSame(rootNode, rootView.getUiViewNode()); + + CanvasViewInfo childView = rootView.getChildren().get(0); + assertEquals("ListView", childView.getName()); + assertEquals(0, childView.getChildren().size()); + assertSame(childNode, childView.getUiViewNode()); + } + /** * Dumps out the given {@link ViewInfo} hierarchy to standard out. * Useful during development. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java index db18762..b29f9f3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java @@ -53,7 +53,7 @@ public class SelectionManagerTest extends TestCase { "android.widget.Button", false); ViewInfo child2 = new ViewInfo("Button2", child2Node, 0, 20, 70, 25); root.setChildren(Arrays.asList(child1, child2)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /* layoutlib5 */).getFirst(); assertNotNull(rootView); manager.selectMultiple(Arrays.asList(rootView, rootView.getChildren().get(0), rootView @@ -104,7 +104,7 @@ public class SelectionManagerTest extends TestCase { "android.widget.Button", false); ViewInfo child2 = new ViewInfo("Button2", child2Node, 0, 20, 70, 25); root.setChildren(Arrays.asList(child1, child2)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /* layoutlib5 */).getFirst(); assertNotNull(rootView); manager.selectMultiple(Arrays.asList(rootView.getChildren().get(0))); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java index 277089f..9f670cc 100755 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java @@ -48,7 +48,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // Create a NodeProxy. NodeProxy proxy = m.create(cvi); @@ -95,7 +95,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // NodeProxies are cached. Creating the same one twice returns the same proxy. NodeProxy proxy1 = m.create(cvi); @@ -107,7 +107,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // NodeProxies are cached. Creating the same one twice returns the same proxy. NodeProxy proxy1 = m.create(cvi); diff --git a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java index e3c93b7..216d694 100644 --- a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java @@ -28,7 +28,7 @@ import java.util.Map; public class ResourceResolver extends RenderResources { - private final static String REFERENCE_STYLE = ResourceType.STYLE.getName() + "/"; + public final static String REFERENCE_STYLE = ResourceType.STYLE.getName() + "/"; public final static String PREFIX_ANDROID_RESOURCE_REF = "@android:"; public final static String PREFIX_RESOURCE_REF = "@"; public final static String PREFIX_ANDROID_THEME_REF = "?android:"; diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java index 2f79038..6620571 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java @@ -24,7 +24,7 @@ public enum Capability { /** Ability to render at full size, as required by the layout, and unbound by the screen */ UNBOUND_RENDERING, /** Ability to override the background of the rendering with transparency using - * {@link SessionParams#setCustomBackgroundColor(int)} */ + * {@link SessionParams#setOverrideBgColor(int)} */ CUSTOM_BACKGROUND_COLOR, /** Ability to call {@link RenderSession#render()} and {@link RenderSession#render(long)}. */ RENDER, diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java index befec28..41c4d02 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java @@ -516,6 +516,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { public void start(Point point) { try { manager.touchDown(point.getX(), point.getY()); + manager.touchMove(point.getX(), point.getY()); } catch (IOException e) { LOG.log(Level.SEVERE, "Error sending drag start event", e); } @@ -529,6 +530,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { public void end(Point point) { try { + manager.touchMove(point.getX(), point.getY()); manager.touchUp(point.getX(), point.getY()); } catch (IOException e) { LOG.log(Level.SEVERE, "Error sending drag end event", e); |