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>
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$
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;
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;
// 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))) {
return null;
}
- ResourceResolver createResolver() {
+ public ResourceResolver createResolver() {
String theme = mConfigComposite.getTheme();
boolean isProjectTheme = mConfigComposite.isProjectTheme();
Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes =
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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) {
+ }
+ }
+}
boolean isValue = false;
boolean isTagName = false;
+ boolean isAttributeName = false;
IStructuredModel model = null;
try {
model = xmlEditor.getModelForRead();
|| 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 {
}
}
- if (isValue || isTagName) {
+ if (isValue || isTagName || isAttributeName) {
StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor();
ISelectionProvider provider = structuredEditor.getSelectionProvider();
ISelection selection = provider.getSelection();
// 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[] {
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)),
};
}
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>();
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;
mProject = editor != null ? editor.getProject() : null;
mSelectionStart = 0;
mSelectionEnd = 0;
+ mOriginalSelectionStart = 0;
+ mOriginalSelectionEnd = 0;
mSelection = null;
mTreeSelection = null;
if (start >= 0) {
mSelectionStart = start;
mSelectionEnd = end;
+ mOriginalSelectionStart = start;
+ mOriginalSelectionEnd = end;
}
}
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();
} 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();
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('"');
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++) {
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
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();
* 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$
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) {
((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();
* (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");
}
}
+
+ 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();
}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
final int offset = caretContextIndex + caretDelta;
- RefactoringAssistant aaptQuickFix = new RefactoringAssistant();
+ RefactoringAssistant refactoringAssistant = new RefactoringAssistant();
// Open file
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
return viewer;
}
};
- ICompletionProposal[] proposals = aaptQuickFix
+ ICompletionProposal[] proposals = refactoringAssistant
.computeQuickAssistProposals(invocationContext);
if (proposals != null) {
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;
}
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;
IDocument document = new Document();
document.set(xml);
+ String before = document.get();
+
TextEdit edit = tf.getEdit();
if (edit instanceof MultiTextEdit) {
MultiTextEdit edits = (MultiTextEdit) edit;
}
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");
--- /dev/null
+< <Button android:text="Button"
+---
+> <Button style="@style/newstyle" android:text="Button"
--- /dev/null
+ android:layout_width="wrap_content" android:layout_height="fill_parent"
+< android:textColor="#FF00FF" android:textSize="20pt"
+ </FrameLayout>
+---
--- /dev/null
+< <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"
--- /dev/null
+< <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"
--- /dev/null
+< <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>
--- /dev/null
+< <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"
--- /dev/null
+< <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>
--- /dev/null
+< <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>
--- /dev/null
+ android:layout_width="wrap_content" android:layout_height="fill_parent"
+< android:textColor="#FF00FF" android:textSize="20pt"
+ </FrameLayout>
+---
--- /dev/null
+android.widget.LinearLayout [0,36,140,320] <LinearLayout>
+ android.widget.Button [0,0,140,62] <Button>
+ android.widget.Button [0,62,140,284] <Button>
--- /dev/null
+<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>
--- /dev/null
+< <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"
--- /dev/null
+android.widget.LinearLayout [0,36,140,320] <LinearLayout>
+ android.widget.Button [0,0,140,62] <Button>
+ android.widget.Button [0,62,140,284] <Button>
--- /dev/null
+<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>
--- /dev/null
+---
+ </style>
+> <style name="newstyle">
+> <item name="android:textColor">#FF00FF</item>
+> <item name="android:textSize">20pt</item>
+ </resources>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
--- /dev/null
+<
+---
+> <?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>
Quick assistant in sample1a.xml for <Button android:text="Fir^stButton":
Extract Android String : Initiates the given refactoring operation
+Extract Style : Initiates the given refactoring operation
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
Quick assistant in sample1a.xml for <Button andr^oid:text="FirstButton":
-None found.
+Extract Style : Initiates the given refactoring operation
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:";