OSDN Git Service

Extract Style Refactoring
authorTor Norbye <tnorbye@google.com>
Mon, 28 Mar 2011 16:16:41 +0000 (09:16 -0700)
committerTor Norbye <tnorbye@google.com>
Thu, 31 Mar 2011 19:40:28 +0000 (12:40 -0700)
Adds a new refactoring, "Extract Style", which will show the user the
attributes for the current selected elements (or if invoked from an
editor context, the attributes overlapping the current caret or editor
selection). The user can select which attributes to extract, and these
are then added as a new style in the styles.xml file in the project
(which is created if necessary). The user can optionally replace the
attributes that were extracted, and the user can also optionally set
the style attribute on the elements to the new style. (Both are on by
default.) This is integrated with the refactoring quick assistant as
well.

Change-Id: I0504e86a824b00730482607150a879ff28233618

47 files changed:
eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt
ide_common/src/com/android/ide/common/resources/ResourceResolver.java

index 9c3f208..82f4e05 100644 (file)
                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"
       </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">
             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>
index 092b0a5..73cb229 100644 (file)
@@ -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$
index b862ada..bccc7d0 100644 (file)
@@ -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;
index a0db1fb..1857602 100644 (file)
@@ -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))) {
index 47d4f22..ad3292e 100644 (file)
@@ -1587,7 +1587,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/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 (file)
index 0000000..4eef6b8
--- /dev/null
@@ -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 (file)
index 0000000..95fbdbc
--- /dev/null
@@ -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 (file)
index 0000000..3e18fca
--- /dev/null
@@ -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 (file)
index 0000000..2828288
--- /dev/null
@@ -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) {
+        }
+    }
+}
index 2de8536..4baad1d 100644 (file)
@@ -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)),
                     };
                 }
index 4b2a0cb..7db0a5c 100644 (file)
@@ -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
index 8ff6b6e..6a2368a 100644 (file)
@@ -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();
 
index fd6bca8..a5a24d7 100644 (file)
@@ -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$
 
@@ -165,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) {
index 2832c1d..07c0fe1 100644 (file)
@@ -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();
index f987729..db74295 100644 (file)
@@ -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 (file)
index 0000000..a7bfbad
--- /dev/null
@@ -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);
+    }
+
+}
index 773c43c..498f65a 100644 (file)
@@ -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) {
index 947840c..18d56a1 100644 (file)
@@ -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");
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 (file)
index 0000000..d734ccf
--- /dev/null
@@ -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 (file)
index 0000000..2ebc91d
--- /dev/null
@@ -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 (file)
index 0000000..ec560b3
--- /dev/null
@@ -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 (file)
index 0000000..ec560b3
--- /dev/null
@@ -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 (file)
index 0000000..f7fd22b
--- /dev/null
@@ -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 (file)
index 0000000..a8e2af4
--- /dev/null
@@ -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 (file)
index 0000000..bcaff2a
--- /dev/null
@@ -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 (file)
index 0000000..1db5e38
--- /dev/null
@@ -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 (file)
index 0000000..2ebc91d
--- /dev/null
@@ -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 (file)
index 0000000..69f7739
--- /dev/null
@@ -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 (file)
index 0000000..64c49b2
--- /dev/null
@@ -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 (file)
index 0000000..84c2ad7
--- /dev/null
@@ -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 (file)
index 0000000..69f7739
--- /dev/null
@@ -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 (file)
index 0000000..3cb966f
--- /dev/null
@@ -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 (file)
index 0000000..141180b
--- /dev/null
@@ -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 (file)
index 0000000..d83eb49
--- /dev/null
@@ -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 (file)
index 0000000..d83eb49
--- /dev/null
@@ -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 (file)
index 0000000..d83eb49
--- /dev/null
@@ -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 (file)
index 0000000..3b4d930
--- /dev/null
@@ -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 (file)
index 0000000..d83eb49
--- /dev/null
@@ -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 (file)
index 0000000..0685d94
--- /dev/null
@@ -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 (file)
index 0000000..f052485
--- /dev/null
@@ -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 (file)
index 0000000..ce1d4aa
--- /dev/null
@@ -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 (file)
index 0000000..51f0812
--- /dev/null
@@ -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 (file)
index 0000000..8f7ad98
--- /dev/null
@@ -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>
index dfa9d15..95187d3 100644 (file)
@@ -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
index e3c93b7..216d694 100644 (file)
@@ -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:";