IDEA-70314 android extract style refactoring

This commit is contained in:
Eugene Kudelevsky
2012-08-01 14:07:42 +04:00
parent 7eb03640d5
commit 8f36645fed
31 changed files with 989 additions and 324 deletions
@@ -15,7 +15,6 @@
*/
package com.intellij.android.designer.propertyTable.editors;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.android.designer.propertyTable.renderers.ResourceRenderer;
@@ -28,7 +27,6 @@ import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.ide.util.treeView.NodeRenderer;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.DialogWrapper;
@@ -38,7 +36,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.*;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.speedSearch.SpeedSearchUtil;
@@ -50,7 +48,7 @@ import org.jetbrains.android.actions.CreateResourceFileAction;
import org.jetbrains.android.actions.CreateXmlResourceDialog;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.refactoring.AndroidExtractStyleAction;
import org.jetbrains.android.resourceManagers.FileResourceProcessor;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.util.AndroidResourceUtil;
@@ -205,15 +203,10 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen
actionGroup.add(myNewResourceFileAction);
}
if (myComponent != null && ResourceType.STYLE.equals(resourceGroup.getType())) {
boolean enabled = false;
for (XmlAttribute attribute : myComponent.getTag().getAttributes()) {
if (attribute.getName().startsWith("android:")) {
enabled = true;
break;
}
}
final XmlTag componentTag = myComponent.getTag();
final boolean enabled = AndroidExtractStyleAction.isEnabled(componentTag);
myExtractStyleAction.getTemplatePresentation().setEnabled(enabled);
//actionGroup.add(myExtractStyleAction); // XXX
actionGroup.add(myExtractStyleAction);
}
return actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, actionGroup);
@@ -263,45 +256,10 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen
}
private void extractStyle() {
String fileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STYLE);
VirtualFile[] dirs = {AndroidRootUtil.getResourceDir(AndroidFacet.getInstance(myModule))};
List<VirtualFile> subDirs = AndroidResourceUtil.getResourceSubdirs(ResourceFolderType.VALUES.getName(), dirs);
List<String> dirNames = new ArrayList<String>();
for (VirtualFile dir : subDirs) {
dirNames.add(dir.getName());
}
ExtractStyleDialog dialog = new ExtractStyleDialog(myModule, fileName, dirNames, myComponent.getTag());
dialog.show();
if (!dialog.isOK()) {
final String resName = AndroidExtractStyleAction.doExtractStyle(myModule, myComponent.getTag(), false, null);
if (resName == null) {
return;
}
final List<XmlAttribute> attributes = dialog.getStyledAttributes();
StringBuilder value = new StringBuilder();
for (XmlAttribute attribute : attributes) {
value.append("<item name=\"").append(attribute.getName()).append("\">").append(attribute.getValue()).append("</item>\n");
}
String resName = dialog.getStyleName();
if (!AndroidResourceUtil.createValueResource(myModule, resName, ResourceType.STYLE, fileName, dirNames, value.toString())) {
return;
}
PsiDocumentManager.getInstance(myModule.getProject()).commitAllDocuments();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (XmlAttribute attribute : attributes) {
attribute.delete();
}
}
});
myResultResourceName = "@style/" + resName;
close(OK_EXIT_CODE);
}
@@ -420,4 +420,5 @@ directory.not.specified.error=Directory is not specified
android.manifest.merger.not.supported.error=Manifest merging is not supported. Please, reconfigure your manifest files
invalid.file.resource.name.error=Resource file name must contain only lowercase a-z, 0-9, or _
invalid.file.resource.name.error1=Invalid resource file name
invalid.resource.name.error=Invalid resource name ''{0}''
invalid.resource.name.error=Invalid resource name ''{0}''
android.extract.style.title=Extract Android Style
+4
View File
@@ -82,6 +82,10 @@
icon="/icons/attachDebugger.png">
<add-to-group group-id="ToolbarRunGroup" anchor="after" relative-to-action="RunnerActions"/>
</action>
<action id="AndroidExtractStyleAction" class="org.jetbrains.android.refactoring.AndroidExtractStyleAction" text="Android St_yle..."
description="Pull out style-related attributes from layout and extract them as a new style">
<add-to-group group-id="IntroduceActionsGroup" relative-to-action="ExtractSuperclass" anchor="after"/>
</action>
</actions>
<depends>JUnit</depends>
@@ -45,6 +45,7 @@ import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -57,12 +58,24 @@ import static org.jetbrains.android.util.AndroidUtils.SYSTEM_RESOURCE_PACKAGE;
*/
@SuppressWarnings({"EnumSwitchStatementWhichMissesCases"})
public class AndroidDomUtil {
public static final StaticEnumConverter BOOLEAN_CONVERTER = new StaticEnumConverter("true", "false");
public static final Map<String, String> SPECIAL_RESOURCE_TYPES = new HashMap<String, String>();
private static final PackageClassConverter ACTIVITY_CONVERTER = new PackageClassConverter(AndroidUtils.ACTIVITY_BASE_CLASS_NAME);
private static final OnClickConverter ON_CLICK_CONVERTER = new OnClickConverter();
private static final FragmentClassConverter FRAGMENT_CLASS_CONVERTER = new FragmentClassConverter();
@NonNls public static final String ATTR_ID = "id";
@NonNls public static final String ATTR_STYLE = "style";
@NonNls public static final String ATTR_LAYOUT_PREFIX = "layout_";
@NonNls public static final String ATTR_LAYOUT_MARGIN = "layout_margin";
@NonNls public static final String ATTR_TEXT = "text";
@NonNls public static final String ATTR_HINT = "hint";
@NonNls public static final String ATTR_SRC = "src";
@NonNls public static final String ATTR_ON_CLICK = "onClick";
static {
addSpecialResourceType(ResourceType.STRING.getName(), "label", "description", "title");
addSpecialResourceType(ResourceType.DRAWABLE.getName(), "icon");
@@ -33,6 +33,8 @@ import java.util.List;
public interface Style extends ResourceElement {
List<StyleItem> getItems();
StyleItem addItem();
@Attribute("parent")
@Convert(ParentStyleConverter.class)
GenericAttributeValue<ResourceValue> getParentStyle();
@@ -0,0 +1,358 @@
package org.jetbrains.android.refactoring;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.sdklib.SdkConstants;
import com.intellij.lang.Language;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.actions.BaseRefactoringAction;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashSet;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.jetbrains.android.dom.AndroidDomUtil;
import org.jetbrains.android.dom.layout.LayoutDomFileDescription;
import org.jetbrains.android.dom.layout.LayoutViewElement;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.ResourceValue;
import org.jetbrains.android.dom.resources.Style;
import org.jetbrains.android.dom.resources.StyleItem;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidExtractStyleAction extends BaseRefactoringAction {
private static String[] NON_EXTRACTABLE_ATTRIBUTES =
new String[]{AndroidDomUtil.ATTR_ID, AndroidDomUtil.ATTR_TEXT, AndroidDomUtil.ATTR_HINT, AndroidDomUtil.ATTR_SRC,
AndroidDomUtil.ATTR_ON_CLICK};
private final MyTestConfig myTestConfig;
public AndroidExtractStyleAction() {
myTestConfig = null;
}
@TestOnly
public AndroidExtractStyleAction(@Nullable MyTestConfig testConfig) {
myTestConfig = testConfig;
}
@Override
protected boolean isAvailableInEditorOnly() {
return false;
}
@Override
protected boolean isAvailableOnElementInEditorAndFile(PsiElement element, Editor editor, PsiFile file, DataContext context) {
if (element == null ||
AndroidFacet.getInstance(element) == null ||
PsiTreeUtil.getParentOfType(element, XmlText.class) != null) {
return false;
}
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
return tag != null && isEnabled(tag);
}
@Override
protected boolean isEnabledOnElements(PsiElement[] elements) {
if (elements.length != 1) {
return false;
}
final PsiElement element = elements[0];
return element instanceof XmlTag &&
AndroidFacet.getInstance(element) != null &&
isEnabled((XmlTag)element);
}
public static boolean isEnabled(@NotNull XmlTag tag) {
return getLayoutViewElement(tag) != null &&
getExtractableAttributes(tag).size() > 0;
}
@Nullable
private static LayoutViewElement getLayoutViewElement(@NotNull XmlTag tag) {
final DomElement domElement = DomManager.getDomManager(tag.getProject()).getDomElement(tag);
return domElement instanceof LayoutViewElement
? (LayoutViewElement)domElement
: null;
}
@Override
protected RefactoringActionHandler getHandler(DataContext dataContext) {
return new MyHandler(myTestConfig);
}
@Override
protected boolean isAvailableForLanguage(Language language) {
return language == XMLLanguage.INSTANCE;
}
@Override
protected boolean isAvailableForFile(PsiFile file) {
return file instanceof XmlFile &&
AndroidFacet.getInstance(file) != null &&
DomManager.getDomManager(file.getProject()).getDomFileDescription((XmlFile)file)
instanceof LayoutDomFileDescription;
}
private static void doExtractStyle(@NotNull XmlTag viewTag, @Nullable MyTestConfig testConfig) {
final Module module = ModuleUtilCore.findModuleForPsiElement(viewTag);
assert module != null;
doExtractStyle(module, viewTag, true, testConfig);
}
@Nullable
public static String doExtractStyle(@NotNull Module module,
@NotNull final XmlTag viewTag,
final boolean addStyleAttributeToTag,
@Nullable MyTestConfig testConfig) {
final PsiFile file = viewTag.getContainingFile();
if (file == null) {
return null;
}
final String dialogTitle = AndroidBundle.message("android.extract.style.title");
final String fileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STYLE);
assert fileName != null;
final List<String> dirNames = Arrays.asList(ResourceFolderType.VALUES.getName());
final List<XmlAttribute> extractableAttributes = getExtractableAttributes(viewTag);
final Project project = module.getProject();
if (extractableAttributes.size() == 0) {
AndroidUtils.reportError(project, "The tag doesn't contain any attributes that can be extracted", dialogTitle);
return null;
}
final LayoutViewElement viewElement = getLayoutViewElement(viewTag);
assert viewElement != null;
final ResourceValue parentStyleVlaue = viewElement.getStyle().getValue();
final String parentStyle;
boolean supportImplicitParent = false;
if (parentStyleVlaue != null) {
parentStyle = parentStyleVlaue.getResourceName();
if (!ResourceType.STYLE.getName().equals(parentStyleVlaue.getResourceType()) || parentStyle == null || parentStyle.length() == 0) {
AndroidUtils.reportError(project, "Invalid parent style reference " + parentStyleVlaue.toString(), dialogTitle);
return null;
}
supportImplicitParent = parentStyleVlaue.getPackage() == null;
}
else {
parentStyle = null;
}
final String styleName;
final List<XmlAttribute> styledAttributes;
final Module chosenModule;
if (testConfig == null) {
final ExtractStyleDialog dialog =
new ExtractStyleDialog(module, fileName, supportImplicitParent ? parentStyle : null, dirNames, extractableAttributes);
dialog.setTitle(dialogTitle);
dialog.show();
if (!dialog.isOK()) {
return null;
}
chosenModule = dialog.getChosenModule();
assert chosenModule != null;
styledAttributes = dialog.getStyledAttributes();
styleName = dialog.getStyleName();
}
else {
testConfig.validate(extractableAttributes);
chosenModule = testConfig.getModule();
styleName = testConfig.getStyleName();
final Set<String> attrsToExtract = new HashSet<String>(Arrays.asList(testConfig.getAttributesToExtract()));
styledAttributes = new ArrayList<XmlAttribute>();
for (XmlAttribute attribute : extractableAttributes) {
if (attrsToExtract.contains(attribute.getName())) {
styledAttributes.add(attribute);
}
}
}
final boolean[] success = {false};
final boolean finalSupportImplicitParent = supportImplicitParent;
new WriteCommandAction(project, "Extract Android Style '" + styleName + "'", file) {
@Override
protected void run(final Result result) throws Throwable {
final List<XmlAttribute> attributesToDelete = new ArrayList<XmlAttribute>();
if (!AndroidResourceUtil
.createValueResource(chosenModule, styleName, ResourceType.STYLE, fileName, dirNames, new Processor<ResourceElement>() {
@Override
public boolean process(ResourceElement element) {
assert element instanceof Style;
final Style style = (Style)element;
for (XmlAttribute attribute : styledAttributes) {
if (SdkConstants.NS_RESOURCES.equals(attribute.getNamespace())) {
final StyleItem item = style.addItem();
item.getName().setStringValue("android:" + attribute.getLocalName());
item.setStringValue(attribute.getValue());
attributesToDelete.add(attribute);
}
}
if (parentStyleVlaue != null && (!finalSupportImplicitParent || !styleName.startsWith(parentStyle + "."))) {
final String aPackage = parentStyleVlaue.getPackage();
style.getParentStyle().setStringValue((aPackage != null ? aPackage + ":" : "") + parentStyle);
}
return true;
}
})) {
return;
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (XmlAttribute attribute : attributesToDelete) {
attribute.delete();
}
if (addStyleAttributeToTag) {
final LayoutViewElement viewElement = getLayoutViewElement(viewTag);
assert viewElement != null;
viewElement.getStyle().setStringValue("@style/" + styleName);
}
}
});
success[0] = true;
}
@Override
protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
return UndoConfirmationPolicy.REQUEST_CONFIRMATION;
}
}.execute();
return success[0] ? styleName : null;
}
@NotNull
static List<XmlAttribute> getExtractableAttributes(@NotNull XmlTag viewTag) {
final List<XmlAttribute> extractableAttributes = new ArrayList<XmlAttribute>();
for (XmlAttribute attribute : viewTag.getAttributes()) {
if (canBeExtracted(attribute)) {
extractableAttributes.add(attribute);
}
}
return extractableAttributes;
}
private static boolean canBeExtracted(@NotNull XmlAttribute attribute) {
if (!(SdkConstants.NS_RESOURCES.equals(attribute.getNamespace()))) {
return false;
}
final String name = attribute.getLocalName();
if (ArrayUtil.find(NON_EXTRACTABLE_ATTRIBUTES, name) >= 0) {
return false;
}
if (name.startsWith(AndroidDomUtil.ATTR_STYLE)) {
return false;
}
if (name.startsWith(AndroidDomUtil.ATTR_LAYOUT_PREFIX) && !name.startsWith(AndroidDomUtil.ATTR_LAYOUT_MARGIN)) {
return false;
}
return true;
}
private static class MyHandler implements RefactoringActionHandler {
private final MyTestConfig myTestConfig;
private MyHandler(@Nullable MyTestConfig testConfig) {
myTestConfig = testConfig;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
final PsiElement element = getElementAtCaret(editor, file);
if (element == null) {
return;
}
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
if (tag == null) {
return;
}
doExtractStyle(tag, myTestConfig);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
if (elements.length != 1) {
return;
}
final PsiElement element = elements[0];
if (!(element instanceof XmlTag)) {
return;
}
doExtractStyle((XmlTag)element, myTestConfig);
}
}
static class MyTestConfig {
private final Module myModule;
private final String myStyleName;
private final String[] myAttributesToExtract;
MyTestConfig(@NotNull Module module,
@NotNull String styleName,
@NotNull String[] attributesToExtract) {
myModule = module;
myStyleName = styleName;
myAttributesToExtract = attributesToExtract;
}
@NotNull
public Module getModule() {
return myModule;
}
@NotNull
public String getStyleName() {
return myStyleName;
}
@NotNull
public String[] getAttributesToExtract() {
return myAttributesToExtract;
}
public void validate(@NotNull List<XmlAttribute> extractableAttributes) {
}
}
}
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.refactoring.ExtractStyleDialog">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="337" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="fabf3" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="6b581"/>
<text value="&amp;Style name:"/>
</properties>
</component>
<component id="6b581" class="javax.swing.JTextField" binding="myStyleNameField">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<grid id="8a6e9" binding="myAttributeListWrapper" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="200"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="7f54d" class="com.intellij.ui.components.JBLabel" binding="myAttributesLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Attributes:"/>
</properties>
</component>
<component id="8a393" class="com.intellij.ui.components.JBLabel" binding="myModuleLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="acdc4"/>
<text value="&amp;Module:"/>
</properties>
</component>
<component id="acdc4" class="javax.swing.JComboBox" binding="myModuleCombo">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
</form>
@@ -1,240 +1,233 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.intellij.android.designer.propertyTable.editors;
import com.android.resources.ResourceType;
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.*;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.android.actions.CreateXmlResourceDialog;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class ExtractStyleDialog extends DialogWrapper {
private final Module myModule;
private final String myFileName;
private final List<String> myDirNames;
private final JPanel myPanel;
private final JTextField myNameText;
private final CheckboxTree myTree;
private final CheckedTreeNode myRootNode;
public ExtractStyleDialog(Module module, String fileName, List<String> dirNames, XmlTag tag) {
super(module.getProject());
myModule = module;
myFileName = fileName;
myDirNames = dirNames;
myPanel = new JPanel(new GridLayoutManager(3, 2));
JLabel nameLabel = new JLabel("Style name:");
nameLabel.setDisplayedMnemonic('n');
myPanel.add(nameLabel,
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 0, 0, null, null, null));
myNameText = new JTextField();
myPanel.add(myNameText, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_GROW, 0, null, null, null));
nameLabel.setLabelFor(myNameText);
myNameText.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
checkFinish();
}
@Override
public void removeUpdate(DocumentEvent e) {
checkFinish();
}
@Override
public void changedUpdate(DocumentEvent e) {
checkFinish();
}
});
JLabel attributesLabel = new JLabel("Attributes:");
attributesLabel.setDisplayedMnemonic('A');
myPanel.add(attributesLabel,
new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 0, 0, null, null, null));
myRootNode = new CheckedTreeNode(null);
for (XmlAttribute attribute : tag.getAttributes()) {
String name = attribute.getName();
if (!"style".equalsIgnoreCase(name) && name.startsWith("android:")) {
CheckedTreeNode treeNode = new CheckedTreeNode(attribute);
treeNode.setChecked(!"android:layout_width".equalsIgnoreCase(name) && !"android:layout_height".equalsIgnoreCase(name));
myRootNode.add(treeNode);
}
}
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (value instanceof CheckedTreeNode) {
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)value).getUserObject();
if (attribute != null) {
getTextRenderer().append(attribute.getLocalName());
getTextRenderer().append(" [" + attribute.getValue() + "]", SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
}
}
}
};
myTree = new CheckboxTree(renderer, myRootNode) {
@Override
protected void onNodeStateChanged(CheckedTreeNode node) {
checkFinish();
}
protected void installSpeedSearch() {
new TreeSpeedSearch(this, new Convertor<TreePath, String>() {
public String convert(TreePath path) {
Object object = path.getLastPathComponent();
if (object instanceof CheckedTreeNode) {
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)object).getUserObject();
if (attribute != null) {
return attribute.getLocalName();
}
}
return "";
}
});
}
};
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myTree.setRootVisible(false);
myTree.setShowsRootHandles(false);
TreeUtil.expandAll(myTree);
attributesLabel.setLabelFor(myTree);
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree);
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
decorator.setEditAction(null);
decorator.disableUpDownActions();
AnActionButton selectAll = new AnActionButton("Select All", null, PlatformIcons.SELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
setChecked(true);
}
};
decorator.addExtraAction(selectAll);
AnActionButton unselectAll = new AnActionButton("Unselect All", null, PlatformIcons.UNSELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
setChecked(false);
}
};
decorator.addExtraAction(unselectAll);
myPanel.add(decorator.createPanel(),
new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_GROW, 0, null, null, null));
myPanel.setPreferredSize(new Dimension(400, -1));
setTitle("Extract Style");
checkFinish();
init();
}
private void checkFinish() {
if (AndroidResourceUtil.isCorrectAndroidResourceName(myNameText.getText().trim())) {
int count = myRootNode.getChildCount();
for (int i = 0; i < count; i++) {
CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
if (treeNode.isChecked()) {
setOKActionEnabled(true);
return;
}
}
}
setOKActionEnabled(false);
}
private void setChecked(boolean value) {
int count = myRootNode.getChildCount();
for (int i = 0; i < count; i++) {
((CheckedTreeNode)myRootNode.getChildAt(i)).setChecked(value);
}
myTree.repaint();
checkFinish();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myNameText;
}
@Override
protected ValidationInfo doValidate() {
return CreateXmlResourceDialog.checkIfResourceAlreadyExists(myModule, getStyleName(), ResourceType.STYLE, myDirNames, myFileName);
}
@NotNull
public String getStyleName() {
return myNameText.getText();
}
@NotNull
public List<XmlAttribute> getStyledAttributes() {
List<XmlAttribute> attributes = new ArrayList<XmlAttribute>();
int count = myRootNode.getChildCount();
for (int i = 0; i < count; i++) {
CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
if (treeNode.isChecked()) {
attributes.add((XmlAttribute)treeNode.getUserObject());
}
}
return attributes;
}
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.jetbrains.android.refactoring;
import com.android.resources.ResourceType;
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.android.actions.CreateXmlResourceDialog;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.android.util.ModuleListCellRendererWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.util.*;
/**
* @author Alexander Lobas
*/
public class ExtractStyleDialog extends DialogWrapper {
private JPanel myPanel;
private JTextField myStyleNameField;
private JPanel myAttributeListWrapper;
private JBLabel myAttributesLabel;
private JBLabel myModuleLabel;
private JComboBox myModuleCombo;
private final Module myModule;
private final String myFileName;
private final List<String> myDirNames;
private final CheckboxTree myTree;
private final CheckedTreeNode myRootNode;
public ExtractStyleDialog(@NotNull Module module,
@NotNull String fileName,
@Nullable String parentStyleName,
@NotNull List<String> dirNames,
@NotNull List<XmlAttribute> attributes) {
super(module.getProject());
myFileName = fileName;
myDirNames = dirNames;
if (parentStyleName != null && parentStyleName.length() > 0) {
myStyleNameField.setText(parentStyleName + ".");
myStyleNameField.selectAll();
}
final Set<Module> modulesSet = new HashSet<Module>();
modulesSet.add(module);
for (AndroidFacet depFacet : AndroidUtils.getAllAndroidDependencies(module, true)) {
modulesSet.add(depFacet.getModule());
}
assert modulesSet.size() > 0;
if (modulesSet.size() == 1) {
myModule = module;
myModuleLabel.setVisible(false);
myModuleCombo.setVisible(false);
}
else {
myModule = null;
final Module[] modules = modulesSet.toArray(new Module[modulesSet.size()]);
Arrays.sort(modules, new Comparator<Module>() {
@Override
public int compare(Module m1, Module m2) {
return m1.getName().compareTo(m2.getName());
}
});
myModuleCombo.setModel(new DefaultComboBoxModel(modules));
myModuleCombo.setSelectedItem(module);
myModuleCombo.setRenderer(new ModuleListCellRendererWrapper(myModuleCombo.getRenderer()));
}
myRootNode = new CheckedTreeNode(null);
for (XmlAttribute attribute : attributes) {
myRootNode.add(new CheckedTreeNode(attribute));
}
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (value instanceof CheckedTreeNode) {
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)value).getUserObject();
if (attribute != null) {
getTextRenderer().append(attribute.getLocalName());
getTextRenderer().append(" [" + attribute.getValue() + "]", SimpleTextAttributes.GRAY_ATTRIBUTES);
}
}
}
};
myTree = new CheckboxTree(renderer, myRootNode) {
protected void installSpeedSearch() {
new TreeSpeedSearch(this, new Convertor<TreePath, String>() {
public String convert(TreePath path) {
Object object = path.getLastPathComponent();
if (object instanceof CheckedTreeNode) {
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)object).getUserObject();
if (attribute != null) {
return attribute.getLocalName();
}
}
return "";
}
});
}
};
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myTree.setRootVisible(false);
myTree.setShowsRootHandles(false);
TreeUtil.expandAll(myTree);
myAttributesLabel.setLabelFor(myTree);
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree);
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
decorator.setEditAction(null);
decorator.disableUpDownActions();
AnActionButton selectAll = new AnActionButton("Select All", null, PlatformIcons.SELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
setChecked(true);
}
};
decorator.addExtraAction(selectAll);
AnActionButton unselectAll = new AnActionButton("Unselect All", null, PlatformIcons.UNSELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
setChecked(false);
}
};
decorator.addExtraAction(unselectAll);
myAttributeListWrapper.add(decorator.createPanel());
init();
}
private void setChecked(boolean value) {
int count = myRootNode.getChildCount();
for (int i = 0; i < count; i++) {
((CheckedTreeNode)myRootNode.getChildAt(i)).setChecked(value);
}
myTree.repaint();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myStyleNameField;
}
@Override
protected ValidationInfo doValidate() {
final String styleName = getStyleName();
if (styleName.length() == 0) {
return new ValidationInfo("specify style name", myStyleNameField);
}
if (!AndroidResourceUtil.isCorrectAndroidResourceName(styleName)) {
return new ValidationInfo("incorrect style name", myStyleNameField);
}
final Module module = getChosenModule();
if (module == null) {
return new ValidationInfo("specify module", myModuleCombo);
}
return CreateXmlResourceDialog.checkIfResourceAlreadyExists(module, getStyleName(), ResourceType.STYLE, myDirNames, myFileName);
}
@NotNull
public String getStyleName() {
return myStyleNameField.getText().trim();
}
@NotNull
public List<XmlAttribute> getStyledAttributes() {
List<XmlAttribute> attributes = new ArrayList<XmlAttribute>();
int count = myRootNode.getChildCount();
for (int i = 0; i < count; i++) {
final CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
if (treeNode.isChecked()) {
attributes.add((XmlAttribute)treeNode.getUserObject());
}
}
return attributes;
}
@Nullable
public Module getChosenModule() {
return myModule != null ? myModule : (Module)myModuleCombo.getSelectedItem();
}
}
@@ -19,7 +19,6 @@ package org.jetbrains.android.util;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.sdklib.SdkConstants;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.CreateElementActionBase;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
@@ -32,7 +31,6 @@ import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModulePackageIndex;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -44,7 +42,6 @@ import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
@@ -268,6 +265,9 @@ public class AndroidResourceUtil {
if (resourceName.length() == 0) {
return false;
}
if (resourceName.startsWith(".") || resourceName.endsWith(".")) {
return false;
}
final String[] identifiers = resourceName.split("\\.");
for (String identifier : identifiers) {
@@ -613,17 +613,17 @@ public class AndroidResourceUtil {
}
public static boolean createValueResource(@NotNull Module module,
@NotNull String resourceName,
@NotNull ResourceType resourceType,
@NotNull String fileName,
@NotNull List<String> dirNames,
@NotNull String value) {
@NotNull String resourceName,
@NotNull final ResourceType resourceType,
@NotNull String fileName,
@NotNull List<String> dirNames,
@NotNull Processor<ResourceElement> afterAddedProcessor) {
final Project project = module.getProject();
final AndroidFacet facet = AndroidFacet.getInstance(module);
assert facet != null;
try {
return addValueResource(facet, resourceName, resourceType, fileName, dirNames, value);
return addValueResource(facet, resourceName, resourceType, fileName, dirNames, afterAddedProcessor);
}
catch (Exception e) {
final String message = CreateElementActionBase.filterMessage(e.getMessage());
@@ -633,18 +633,40 @@ public class AndroidResourceUtil {
}
else {
LOG.info(e);
reportError(project, message);
AndroidUtils.reportError(project, message);
}
return false;
}
}
public static boolean createValueResource(@NotNull Module module,
@NotNull String resourceName,
@NotNull final ResourceType resourceType,
@NotNull String fileName,
@NotNull List<String> dirNames,
@NotNull final String value) {
return createValueResource(module, resourceName, resourceType, fileName, dirNames, new Processor<ResourceElement>() {
@Override
public boolean process(ResourceElement element) {
if (value.length() > 0) {
element.setStringValue(value);
}
else if (resourceType == ResourceType.STYLEABLE ||
resourceType == ResourceType.STYLE) {
element.setStringValue("value");
element.getXmlTag().getValue().setText("");
}
return true;
}
});
}
private static boolean addValueResource(@NotNull AndroidFacet facet,
@NotNull final String resourceName,
@NotNull final ResourceType resourceType,
@NotNull String fileName,
@NotNull List<String> dirNames,
@NotNull final String value) throws Exception {
@NotNull final Processor<ResourceElement> afterAddedProcessor) throws Exception {
if (dirNames.size() == 0) {
return false;
}
@@ -666,7 +688,7 @@ public class AndroidResourceUtil {
for (int i = 0; i < resFiles.length; i++) {
final Resources resources = AndroidUtils.loadDomElement(facet.getModule(), resFiles[i], Resources.class);
if (resources == null) {
reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName));
AndroidUtils.reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName));
return false;
}
resourcesElements[i] = resources;
@@ -678,15 +700,7 @@ public class AndroidResourceUtil {
for (Resources resources : resourcesElements) {
final ResourceElement element = addValueResource(resourceType, resources);
element.getName().setValue(resourceName);
if (value.length() > 0) {
element.setStringValue(value);
}
else if (resourceType == ResourceType.STYLEABLE ||
resourceType == ResourceType.STYLE) {
element.setStringValue("value");
element.getXmlTag().getValue().setText("");
}
afterAddedProcessor.process(element);
}
}
});
@@ -702,14 +716,14 @@ public class AndroidResourceUtil {
final VirtualFile resDir = facet.getLocalResourceManager().getResourceDir();
if (resDir == null) {
reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName()));
AndroidUtils.reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName()));
return null;
}
final VirtualFile dir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, dirName);
final String dirPath = FileUtil.toSystemDependentName(resDir.getPath() + '/' + dirName);
if (dir == null) {
reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath));
AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath));
return null;
}
@@ -722,20 +736,11 @@ public class AndroidResourceUtil {
.createFromTemplate(project, dir, AndroidFileTemplateProvider.VALUE_RESOURCE_FILE_TEMPLATE, fileName);
final VirtualFile result = dir.findChild(fileName);
if (result == null) {
reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName));
AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName));
}
return result;
}
private static void reportError(@NotNull Project project, @NotNull String message) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new IncorrectOperationException(message);
}
else {
Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle());
}
}
@Nullable
public static Pair<String, String> getReferredResourceField(@NotNull AndroidFacet facet, @NotNull PsiReferenceExpression exp) {
final String resFieldName = exp.getReferenceName();
@@ -20,6 +20,7 @@ import com.android.ddmlib.IDevice;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.sdklib.SdkConstants;
import com.intellij.CommonBundle;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.codeInsight.navigation.NavigationUtil;
import com.intellij.execution.ExecutionException;
@@ -69,6 +70,7 @@ import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.content.impl.ContentImpl;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PsiNavigateUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
@@ -721,8 +723,8 @@ public class AndroidUtils {
public static void checkPassword(char[] password) throws CommitStepException {
if (password.length == 0) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
}
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
}
}
public static void checkPassword(JPasswordField passwordField) throws CommitStepException {
@@ -743,4 +745,18 @@ public class AndroidUtils {
}
return result;
}
public static void reportError(@NotNull Project project, @NotNull String message) {
reportError(project, message, CommonBundle.getErrorTitle());
}
public static void reportError(@NotNull Project project, @NotNull String message, @NotNull String title) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new IncorrectOperationException(message);
}
else {
Messages.showErrorDialog(project, message, title);
}
}
}
@@ -0,0 +1,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:background="@android:color/white"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello" style="@style/myStyle"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<resources>
<style name="myStyle">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:background="@android:color/white"
style="@style/style1"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/style1.s"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<resources>
<style name="style1">
<item name="android:textColor">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,9 @@
<resources>
<style name="style1">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="style1.s">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:background="@android:color/white"
style="@style/style1"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/style2"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<resources>
<style name="style1">
<item name="android:textColor">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,9 @@
<resources>
<style name="style1">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="style2" parent="style1">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:background="@android:color/white"
style="@android:style/Widget.Button"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/style1.s"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<resources>
<style name="style1.s" parent="android:Widget.Button">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Butt<caret>on android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/style1"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/style1"
android:text="Hello">
<caret>
</Button>
</LinearLayout>
@@ -0,0 +1,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:col<caret>or/black"
android:background="@android:color/white"
android:text="Hello"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello" style="@style/myStyle"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<resources>
<style name="myStyle">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,3 @@
<resources>
<caret>
</resources>
@@ -0,0 +1,123 @@
package org.jetbrains.android.refactoring;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.util.ArrayUtil;
import org.jetbrains.android.AndroidTestCase;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidExtractStyleTest extends AndroidTestCase {
private static final String BASE_PATH = "refactoring/extractStyle/";
public void test1() {
doTest("myStyle", new String[]{"android:textColor", "android:background"}, false);
}
public void test2() {
doTest("style1.s", new String[]{"android:textColor", "android:background"}, true);
}
public void test3() {
doTest("style2", new String[]{"android:textColor", "android:background"}, true);
}
public void test4() {
doTest("style1.s", new String[]{"android:textColor", "android:background"}, false);
}
public void test5() {
doTestDisabled();
}
public void test6() {
doTestDisabled();
}
public void test7() {
doTest("myStyle", new String[]{"android:textColor", "android:background"}, false);
}
public void test8() {
doTestHidden();
}
private void doTestHidden() {
final String testName = getTestName(true);
final VirtualFile f =
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/values/test" + testName + ".xml");
myFixture.configureFromExistingVirtualFile(f);
final Presentation presentation = myFixture.testAction(new AndroidExtractStyleAction());
assertFalse(presentation.isVisible());
}
private void doTestDisabled() {
final String testName = getTestName(true);
final VirtualFile f =
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test" + testName + ".xml");
myFixture.configureFromExistingVirtualFile(f);
final Presentation presentation = myFixture.testAction(new AndroidExtractStyleAction());
assertFalse(presentation.isEnabled());
assertTrue(presentation.isVisible());
}
private void doTest(@NotNull String styleName,
@NotNull String[] attributes,
boolean copyInitialStylesXml) {
doTest(styleName, attributes, attributes, copyInitialStylesXml);
}
private void doTest(@NotNull String styleName,
@NotNull String[] attributesToExtract,
@NotNull String[] expectedExtractableAttrs,
boolean copyInitialStylesXml) {
final String testName = getTestName(true);
if (copyInitialStylesXml) {
myFixture.copyFileToProject(BASE_PATH + testName + "_styles.xml", "res/values/styles.xml");
}
final VirtualFile f =
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test" + testName + ".xml");
myFixture.configureFromExistingVirtualFile(f);
myFixture.testAction(new AndroidExtractStyleAction(new MyConfig(myModule, styleName, attributesToExtract, expectedExtractableAttrs)));
myFixture.checkResultByFile(BASE_PATH + testName + "_after.xml");
myFixture.checkResultByFile("res/values/styles.xml", BASE_PATH + testName + "_styles_after.xml", true);
}
private static class MyConfig extends AndroidExtractStyleAction.MyTestConfig {
private final String[] myExpectedExtractableAttributes;
MyConfig(@NotNull Module module,
@NotNull String styleName,
@NotNull String[] attributesToExtract,
@NotNull String[] expectedExtractableAttributes) {
super(module, styleName, attributesToExtract);
myExpectedExtractableAttributes = expectedExtractableAttributes;
Arrays.sort(myExpectedExtractableAttributes);
}
@Override
public void validate(@NotNull List<XmlAttribute> extractableAttributes) {
final List<String> names = new ArrayList<String>(extractableAttributes.size());
for (XmlAttribute attribute : extractableAttributes) {
names.add(attribute.getName());
}
final String[] extractableAttributeNames = ArrayUtil.toStringArray(names);
Arrays.sort(extractableAttributeNames);
assertTrue(
"Expected: " + Arrays.toString(myExpectedExtractableAttributes) + "\nActual: " + Arrays.toString(extractableAttributeNames),
Arrays.equals(myExpectedExtractableAttributes, extractableAttributeNames)
);
}
}
}