mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
improve extract style refactoring: use style where possible after is is extracted
This commit is contained in:
@@ -433,4 +433,5 @@ android.inline.file.inline.all.text=Inline &all references and remove the file
|
||||
android.inline.file.inline.this.text=Inline &this usage and keep the file
|
||||
android.inline.layout.title=Inline Android Layout
|
||||
android.facet.importing.notification.group=Importing Error
|
||||
android.facet.importing.title=Error when importing module ''{0}''
|
||||
android.facet.importing.title=Error when importing module ''{0}''
|
||||
android.find.style.applications.title=Use Style Where Possible
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
</action>
|
||||
<action id="AndroidInlineIncludeAction" class="org.jetbrains.android.refactoring.AndroidInlineIncludeAction"
|
||||
text="Inline _Layout..." description="Inlines included Android layout"/>
|
||||
<action id="AndroidFindStyleApplicationsAction" class="org.jetbrains.android.refactoring.AndroidFindStyleApplicationsAction"
|
||||
text="Use style _where possible..." description="Replaces attributes by Android style reference where possible">
|
||||
<add-to-group group-id="RefactoringMenu"/>
|
||||
</action>
|
||||
</actions>
|
||||
|
||||
<depends>JUnit</depends>
|
||||
|
||||
@@ -20,14 +20,13 @@ import com.android.resources.ResourceType;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.xml.Converter;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.ResolvingConverter;
|
||||
import com.intellij.util.xml.XmlName;
|
||||
import com.intellij.util.xml.*;
|
||||
import org.jetbrains.android.dom.attrs.AttributeDefinition;
|
||||
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
|
||||
import org.jetbrains.android.dom.attrs.AttributeFormat;
|
||||
@@ -358,4 +357,29 @@ public class AndroidDomUtil {
|
||||
}
|
||||
return class2Name.values();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AndroidResourceReferenceBase getAndroidResourceReference(@Nullable GenericAttributeValue<ResourceValue> attribute,
|
||||
boolean localOnly) {
|
||||
if (attribute == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ResourceValue resValue = attribute.getValue();
|
||||
if (resValue == null || (localOnly && resValue.getPackage() != null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final XmlAttributeValue value = attribute.getXmlAttributeValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (PsiReference reference : value.getReferences()) {
|
||||
if (reference instanceof AndroidResourceReferenceBase) {
|
||||
return (AndroidResourceReferenceBase)reference;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -16,8 +17,9 @@ class AndroidAttributeInfo {
|
||||
myPackage = aPackage;
|
||||
}
|
||||
|
||||
public boolean isSystem() {
|
||||
return AndroidUtils.SYSTEM_RESOURCE_PACKAGE.equals(myPackage);
|
||||
public String getNamespace() {
|
||||
final boolean system = AndroidUtils.SYSTEM_RESOURCE_PACKAGE.equals(myPackage);
|
||||
return system ? SdkConstants.NS_RESOURCES : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+31
-4
@@ -10,10 +10,11 @@ import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import org.jetbrains.android.dom.AndroidDomUtil;
|
||||
@@ -106,6 +107,7 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
final String styleName;
|
||||
final List<XmlAttribute> styledAttributes;
|
||||
final Module chosenModule;
|
||||
final boolean searchStyleApplications;
|
||||
|
||||
if (testConfig == null) {
|
||||
final ExtractStyleDialog dialog =
|
||||
@@ -116,6 +118,7 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
if (!dialog.isOK()) {
|
||||
return null;
|
||||
}
|
||||
searchStyleApplications = dialog.isToSearchStyleApplications();
|
||||
chosenModule = dialog.getChosenModule();
|
||||
assert chosenModule != null;
|
||||
|
||||
@@ -135,8 +138,10 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
styledAttributes.add(attribute);
|
||||
}
|
||||
}
|
||||
searchStyleApplications = false;
|
||||
}
|
||||
final boolean[] success = {false};
|
||||
final Ref<Style> createdStyleRef = Ref.create();
|
||||
final boolean finalSupportImplicitParent = supportImplicitParent;
|
||||
|
||||
new WriteCommandAction(project, "Extract Android Style '" + styleName + "'", file) {
|
||||
@@ -150,6 +155,7 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
public boolean process(ResourceElement element) {
|
||||
assert element instanceof Style;
|
||||
final Style style = (Style)element;
|
||||
createdStyleRef.set(style);
|
||||
|
||||
for (XmlAttribute attribute : styledAttributes) {
|
||||
if (SdkConstants.NS_RESOURCES.equals(attribute.getNamespace())) {
|
||||
@@ -162,7 +168,7 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
|
||||
if (parentStyleValue != null && (!finalSupportImplicitParent || !styleName.startsWith(parentStyle + "."))) {
|
||||
final String aPackage = parentStyleValue.getPackage();
|
||||
style.getParentStyle().setStringValue((aPackage != null ? aPackage + ":" : "") + parentStyle);
|
||||
style.getParentStyle().setStringValue((aPackage != null ? aPackage + ":" : "") + parentStyle);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -192,7 +198,28 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
}
|
||||
}.execute();
|
||||
|
||||
return success[0] ? styleName : null;
|
||||
if (!success[0]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Style createdStyle = createdStyleRef.get();
|
||||
final XmlTag createdStyleTag = createdStyle != null ? createdStyle.getXmlTag() : null;
|
||||
|
||||
if (createdStyleTag != null) {
|
||||
final AndroidFindStyleApplicationsAction.MyStyleData createdStyleData =
|
||||
AndroidFindStyleApplicationsAction.getStyleData(createdStyleTag);
|
||||
|
||||
if (createdStyleData != null && searchStyleApplications) {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AndroidFindStyleApplicationsAction.doRefactoringForTag(
|
||||
project, createdStyleTag, createdStyleData, file, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return styleName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -212,7 +239,7 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio
|
||||
return false;
|
||||
}
|
||||
final String name = attribute.getLocalName();
|
||||
if (ArrayUtil.find(NON_EXTRACTABLE_ATTRIBUTES, name) >= 0) {
|
||||
if (ArrayUtilRt.find(NON_EXTRACTABLE_ATTRIBUTES, name) >= 0) {
|
||||
return false;
|
||||
}
|
||||
if (name.startsWith(AndroidDomUtil.ATTR_STYLE)) {
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.DomManager;
|
||||
import com.intellij.util.xml.GenericAttributeValue;
|
||||
import org.jetbrains.android.dom.resources.ResourcesDomFileDescription;
|
||||
import org.jetbrains.android.dom.resources.Style;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.resourceManagers.ResourceManager;
|
||||
import org.jetbrains.android.resourceManagers.ValueResourceInfoImpl;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.ErrorReporter;
|
||||
import org.jetbrains.android.util.ProjectBasedErrorReporter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidFindStyleApplicationsAction extends AndroidBaseXmlRefactoringAction {
|
||||
|
||||
private final MyTestConfig myTestConfig;
|
||||
|
||||
public AndroidFindStyleApplicationsAction() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public AndroidFindStyleApplicationsAction(@Nullable MyTestConfig testConfig) {
|
||||
myTestConfig = testConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledForTags(@NotNull XmlTag[] tags) {
|
||||
if (tags.length != 1) {
|
||||
return false;
|
||||
}
|
||||
final MyStyleData data = getStyleData(tags[0]);
|
||||
return data != null && data.getStyle().getItems().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyFile(PsiFile file) {
|
||||
return DomManager.getDomManager(file.getProject()).getDomFileDescription(
|
||||
(XmlFile)file) instanceof ResourcesDomFileDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRefactorForTags(@NotNull Project project, @NotNull XmlTag[] tags) {
|
||||
assert tags.length == 1;
|
||||
final XmlTag tag = tags[0];
|
||||
|
||||
final MyStyleData styleData = getStyleData(tag);
|
||||
assert styleData != null;
|
||||
|
||||
doRefactoringForTag(project, tag, styleData, null, myTestConfig);
|
||||
}
|
||||
|
||||
static void doRefactoringForTag(Project project, XmlTag tag, MyStyleData styleData, PsiFile context, MyTestConfig testConfig) {
|
||||
final ErrorReporter errorReporter = new ProjectBasedErrorReporter(project);
|
||||
final Style style = styleData.getStyle();
|
||||
final Map<AndroidAttributeInfo, String> attrMap =
|
||||
AndroidRefactoringUtil.computeAttributeMap(style, new ProjectBasedErrorReporter(project),
|
||||
AndroidBundle.message("android.find.style.applications.title"));
|
||||
|
||||
if (attrMap == null || attrMap.size() == 0) {
|
||||
return;
|
||||
}
|
||||
final AndroidFacet facet = styleData.getFacet();
|
||||
final Module module = facet.getModule();
|
||||
final StyleRefData parentStyleRef = AndroidRefactoringUtil.getParentStyle(style);
|
||||
PsiElement parentStyleAttrName = null;
|
||||
|
||||
if (parentStyleRef != null) {
|
||||
parentStyleAttrName = resolveStyleRef(parentStyleRef, facet);
|
||||
|
||||
if (parentStyleAttrName == null) {
|
||||
errorReporter.report("Cannot resolve parent style '" + parentStyleRef.getStyleName() + "'",
|
||||
AndroidBundle.message("android.find.style.applications.title"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
final AndroidFindStyleApplicationsProcessor processor = new AndroidFindStyleApplicationsProcessor(
|
||||
module, attrMap, styleData.getName(), tag, styleData.getNameAttrValue(), parentStyleAttrName, context);
|
||||
final VirtualFile contextVFile = context != null ? context.getVirtualFile() : null;
|
||||
|
||||
if (testConfig != null) {
|
||||
processor.configureScope(testConfig.getScope(), contextVFile);
|
||||
processor.run();
|
||||
return;
|
||||
}
|
||||
processor.setPreviewUsages(true);
|
||||
|
||||
final boolean showModuleRadio = AndroidFindStyleApplicationsProcessor.getAllModulesToScan(module).size() > 1;
|
||||
|
||||
if (showModuleRadio || contextVFile != null) {
|
||||
final AndroidFindStyleApplicationsDialog dialog = new AndroidFindStyleApplicationsDialog(
|
||||
contextVFile, processor, showModuleRadio);
|
||||
dialog.show();
|
||||
}
|
||||
else {
|
||||
processor.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiElement resolveStyleRef(StyleRefData styleRef, AndroidFacet facet) {
|
||||
final ResourceManager resourceManager = facet.getResourceManager(styleRef.getStylePackage());
|
||||
|
||||
if (resourceManager == null) {
|
||||
return null;
|
||||
}
|
||||
final List<ValueResourceInfoImpl> infos = resourceManager.findValueResourceInfos(
|
||||
ResourceType.STYLE.getName(), styleRef.getStyleName(), true, false);
|
||||
return infos.size() == 1 ? infos.get(0).computeXmlElement() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static MyStyleData getStyleData(@NotNull XmlTag tag) {
|
||||
final DomElement element = DomManager.getDomManager(tag.getProject()).getDomElement(tag);
|
||||
|
||||
if (!(element instanceof Style)) {
|
||||
return null;
|
||||
}
|
||||
final Style style = (Style)element;
|
||||
final GenericAttributeValue<String> styleNameDomAttr = style.getName();
|
||||
final String styleName = styleNameDomAttr.getStringValue();
|
||||
final XmlAttributeValue styleNameAttrValue = styleNameDomAttr.getXmlAttributeValue();
|
||||
|
||||
if (styleName == null ||
|
||||
styleName.length() == 0 ||
|
||||
styleNameAttrValue == null) {
|
||||
return null;
|
||||
}
|
||||
final AndroidFacet facet = AndroidFacet.getInstance(tag);
|
||||
|
||||
if (facet == null) {
|
||||
return null;
|
||||
}
|
||||
return new MyStyleData(style, styleName, facet, styleNameAttrValue);
|
||||
}
|
||||
|
||||
static class MyStyleData {
|
||||
private final Style myStyle;
|
||||
private final String myName;
|
||||
private final XmlAttributeValue myNameAttrValue;
|
||||
private final AndroidFacet myFacet;
|
||||
|
||||
private MyStyleData(@NotNull Style style,
|
||||
@NotNull String name,
|
||||
@NotNull AndroidFacet facet,
|
||||
@NotNull XmlAttributeValue nameAttrValue) {
|
||||
myStyle = style;
|
||||
myName = name;
|
||||
myFacet = facet;
|
||||
myNameAttrValue = nameAttrValue;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Style getStyle() {
|
||||
return myStyle;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidFacet getFacet() {
|
||||
return myFacet;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public XmlAttributeValue getNameAttrValue() {
|
||||
return myNameAttrValue;
|
||||
}
|
||||
}
|
||||
|
||||
static class MyTestConfig {
|
||||
private final AndroidFindStyleApplicationsProcessor.MyScope myScope;
|
||||
|
||||
MyTestConfig(@NotNull AndroidFindStyleApplicationsProcessor.MyScope scope) {
|
||||
myScope = scope;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidFindStyleApplicationsProcessor.MyScope getScope() {
|
||||
return myScope;
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.refactoring.AndroidFindStyleApplicationsDialog">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" 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="500" height="173"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="72d68" class="com.intellij.ui.components.JBLabel" binding="myCaptionLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="3bf55">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<grid id="ca999" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="f9ca5" class="com.intellij.ui.components.JBRadioButton" binding="myProjectScopeRadio">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Whole project"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="4ebf" class="com.intellij.ui.components.JBRadioButton" binding="myModuleScopeRadio">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="235b" class="com.intellij.ui.components.JBRadioButton" binding="myFileScopeRadio">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Current &file"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<buttonGroups>
|
||||
<group name="myGroup1">
|
||||
<member id="f9ca5"/>
|
||||
<member id="4ebf"/>
|
||||
<member id="235b"/>
|
||||
</group>
|
||||
</buttonGroups>
|
||||
</form>
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.intellij.analysis.AnalysisScopeBundle;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidFindStyleApplicationsDialog extends DialogWrapper {
|
||||
private JPanel myPanel;
|
||||
private JBRadioButton myModuleScopeRadio;
|
||||
private JBRadioButton myFileScopeRadio;
|
||||
private JBRadioButton myProjectScopeRadio;
|
||||
private JBLabel myCaptionLabel;
|
||||
|
||||
private final VirtualFile myFile;
|
||||
private final AndroidFindStyleApplicationsProcessor myProcessor;
|
||||
|
||||
private static final String FIND_STYLE_APPLICATIONS_SCOPE_PROPERTY = "ANDROID_FIND_STYLE_APPLICATION_SCOPE";
|
||||
|
||||
protected AndroidFindStyleApplicationsDialog(@Nullable VirtualFile file,
|
||||
@NotNull AndroidFindStyleApplicationsProcessor processor,
|
||||
boolean showModuleRadio) {
|
||||
super(processor.getModule().getProject(), true);
|
||||
|
||||
myFile = file;
|
||||
myProcessor = processor;
|
||||
|
||||
final Module module = processor.getModule();
|
||||
myModuleScopeRadio.setText(AnalysisScopeBundle.message("scope.option.module.with.mnemonic", module.getName()));
|
||||
myModuleScopeRadio.setVisible(showModuleRadio);
|
||||
|
||||
if (file != null) {
|
||||
myFileScopeRadio.setText("File '" + file.getName() + "'");
|
||||
}
|
||||
else {
|
||||
myFileScopeRadio.setVisible(false);
|
||||
}
|
||||
final String scopeValue = PropertiesComponent.getInstance().getValue(FIND_STYLE_APPLICATIONS_SCOPE_PROPERTY);
|
||||
AndroidFindStyleApplicationsProcessor.MyScope scope;
|
||||
try {
|
||||
scope = Enum.valueOf(AndroidFindStyleApplicationsProcessor.MyScope.class, scopeValue);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
scope = null;
|
||||
}
|
||||
|
||||
if (scope == null) {
|
||||
scope = AndroidFindStyleApplicationsProcessor.MyScope.FILE;
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case PROJECT:
|
||||
myProjectScopeRadio.setSelected(true);
|
||||
break;
|
||||
case MODULE:
|
||||
myModuleScopeRadio.setSelected(true);
|
||||
break;
|
||||
case FILE:
|
||||
myFileScopeRadio.setSelected(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (myModuleScopeRadio.isSelected() && !myModuleScopeRadio.isVisible() ||
|
||||
myFileScopeRadio.isSelected() && !myFileScopeRadio.isVisible()) {
|
||||
myProjectScopeRadio.setSelected(true);
|
||||
}
|
||||
myCaptionLabel.setText("Choose a scope where to search possible applications of style '" + myProcessor.getStyleName() + "'");
|
||||
setTitle(AndroidBundle.message("android.find.style.applications.title"));
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
AndroidFindStyleApplicationsProcessor.MyScope scope;
|
||||
|
||||
if (myModuleScopeRadio.isSelected()) {
|
||||
scope = AndroidFindStyleApplicationsProcessor.MyScope.MODULE;
|
||||
}
|
||||
else if (myProjectScopeRadio.isSelected()) {
|
||||
scope = AndroidFindStyleApplicationsProcessor.MyScope.PROJECT;
|
||||
}
|
||||
else {
|
||||
scope = AndroidFindStyleApplicationsProcessor.MyScope.FILE;
|
||||
}
|
||||
PropertiesComponent.getInstance().setValue(FIND_STYLE_APPLICATIONS_SCOPE_PROPERTY, scope.name());
|
||||
|
||||
myProcessor.configureScope(scope, myFile);
|
||||
myProcessor.setPrepareSuccessfulSwingThreadCallback(new Runnable() {
|
||||
public void run() {
|
||||
close(DialogWrapper.OK_EXIT_CODE);
|
||||
}
|
||||
});
|
||||
myProcessor.run();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.ide.highlighter.XmlFileType;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.undo.UndoUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.XmlRecursiveElementVisitor;
|
||||
import com.intellij.psi.impl.cache.CacheManager;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.UsageSearchContext;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usageView.UsageViewDescriptor;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
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.converters.AndroidResourceReferenceBase;
|
||||
import org.jetbrains.android.dom.layout.LayoutDomFileDescription;
|
||||
import org.jetbrains.android.dom.layout.LayoutViewElement;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.resourceManagers.ValueResourceInfoImpl;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidFindStyleApplicationsProcessor extends BaseRefactoringProcessor {
|
||||
private final Module myModule;
|
||||
private final Map<AndroidAttributeInfo, String> myAttrMap;
|
||||
private final String myStyleName;
|
||||
private final XmlTag myStyleTag;
|
||||
private final XmlAttributeValue myStyleNameAttrValue;
|
||||
private final PsiElement myParentStyleNameAttrValue;
|
||||
private final PsiFile myContext;
|
||||
private boolean mySearchOnlyInCurrentModule;
|
||||
private VirtualFile myFileToScan;
|
||||
|
||||
protected AndroidFindStyleApplicationsProcessor(@NotNull Module module,
|
||||
@NotNull Map<AndroidAttributeInfo, String> attrMap,
|
||||
@NotNull String styleName,
|
||||
@NotNull XmlTag styleTag,
|
||||
@NotNull XmlAttributeValue styleNameAttrValue,
|
||||
@Nullable PsiElement parentStyleNameAttrValue,
|
||||
@Nullable PsiFile context) {
|
||||
super(module.getProject());
|
||||
myModule = module;
|
||||
myAttrMap = attrMap;
|
||||
myStyleName = styleName;
|
||||
myStyleTag = styleTag;
|
||||
myParentStyleNameAttrValue = parentStyleNameAttrValue;
|
||||
myStyleNameAttrValue = styleNameAttrValue;
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
|
||||
return new UsageViewDescriptorAdapter() {
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElement[] getElements() {
|
||||
return new PsiElement[]{myStyleTag};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessedElementsHeader() {
|
||||
return "Style to use";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeReferencesText(int usagesCount, int filesCount) {
|
||||
return "Tags the reference to the style will be added to " +
|
||||
UsageViewBundle.getOccurencesString(usagesCount, filesCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected UsageInfo[] findUsages() {
|
||||
final List<UsageInfo> usages = findAllStyleApplications();
|
||||
return usages.toArray(new UsageInfo[usages.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
|
||||
super.preprocessUsages(refUsages);
|
||||
|
||||
if (refUsages.get().length == 0) {
|
||||
Messages.showInfoMessage(myProject, "IDEA has not found any possible applications of style '" + myStyleName + "'",
|
||||
AndroidBundle.message("android.find.style.applications.title"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performRefactoring(UsageInfo[] usages) {
|
||||
final Set<Pair<String, String>> attrsInStyle = new HashSet<Pair<String, String>>();
|
||||
|
||||
for (AndroidAttributeInfo info : myAttrMap.keySet()) {
|
||||
attrsInStyle.add(Pair.create(info.getNamespace(), info.getName()));
|
||||
}
|
||||
|
||||
for (UsageInfo usage : usages) {
|
||||
final PsiElement element = usage.getElement();
|
||||
final DomElement domElement = element instanceof XmlTag
|
||||
? DomManager.getDomManager(myProject).getDomElement((XmlTag)element)
|
||||
: null;
|
||||
if (domElement instanceof LayoutViewElement) {
|
||||
final List<XmlAttribute> attributesToDelete = new ArrayList<XmlAttribute>();
|
||||
|
||||
for (XmlAttribute attribute : ((XmlTag)element).getAttributes()) {
|
||||
if (attrsInStyle.contains(Pair.create(attribute.getNamespace(),
|
||||
attribute.getLocalName()))) {
|
||||
attributesToDelete.add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (XmlAttribute attribute : attributesToDelete) {
|
||||
attribute.delete();
|
||||
}
|
||||
((LayoutViewElement)domElement).getStyle().setStringValue("@style/" + myStyleName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
final PsiFile file = myStyleTag.getContainingFile();
|
||||
|
||||
if (file != null) {
|
||||
UndoUtil.markPsiFileForUndo(file);
|
||||
}
|
||||
|
||||
if (myContext != null) {
|
||||
UndoUtil.markPsiFileForUndo(myContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCommandName() {
|
||||
return "Use Style '" + myStyleName + "' Where Possible";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<Module> getAllModulesToScan(@NotNull Module module) {
|
||||
final List<Module> result = new ArrayList<Module>();
|
||||
|
||||
for (Module m : ModuleManager.getInstance(module.getProject()).getModules()) {
|
||||
if (m.equals(module) || ModuleRootManager.getInstance(m).isDependsOn(module)) {
|
||||
result.add(module);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<UsageInfo> findAllStyleApplications() {
|
||||
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
|
||||
final Project project = myModule.getProject();
|
||||
final List<VirtualFile> resDirs = new ArrayList<VirtualFile>();
|
||||
|
||||
if (mySearchOnlyInCurrentModule) {
|
||||
collectResDir(myModule, myStyleNameAttrValue, myStyleName, resDirs);
|
||||
}
|
||||
else {
|
||||
for (Module m : getAllModulesToScan(myModule)) {
|
||||
collectResDir(m, myStyleNameAttrValue, myStyleName, resDirs);
|
||||
}
|
||||
}
|
||||
final List<VirtualFile> subdirs = AndroidResourceUtil.getResourceSubdirs(
|
||||
ResourceType.LAYOUT.getName(), resDirs.toArray(new VirtualFile[resDirs.size()]));
|
||||
|
||||
List<VirtualFile> filesToProcess = new ArrayList<VirtualFile>();
|
||||
|
||||
for (VirtualFile subdir : subdirs) {
|
||||
for (VirtualFile child : subdir.getChildren()) {
|
||||
if (child.getFileType() == XmlFileType.INSTANCE &&
|
||||
(myFileToScan == null || myFileToScan.equals(child))) {
|
||||
filesToProcess.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToProcess.size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Set<PsiFile> psiFilesToProcess = new HashSet<PsiFile>();
|
||||
|
||||
for (VirtualFile file : filesToProcess) {
|
||||
final PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
|
||||
|
||||
if (psiFile != null) {
|
||||
psiFilesToProcess.add(psiFile);
|
||||
}
|
||||
}
|
||||
final CacheManager cacheManager = CacheManager.SERVICE.getInstance(project);
|
||||
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(project);
|
||||
|
||||
for (Map.Entry<AndroidAttributeInfo, String> entry : myAttrMap.entrySet()) {
|
||||
filterFilesToScan(cacheManager, entry.getKey().getName(), psiFilesToProcess, projectScope);
|
||||
filterFilesToScan(cacheManager, entry.getValue(), psiFilesToProcess, projectScope);
|
||||
}
|
||||
|
||||
if (psiFilesToProcess.size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final int n = psiFilesToProcess.size();
|
||||
int i = 0;
|
||||
|
||||
if (indicator != null) {
|
||||
indicator.setText("Searching for style applications");
|
||||
}
|
||||
final List<UsageInfo> usages = new ArrayList<UsageInfo>();
|
||||
|
||||
for (PsiFile psiFile : psiFilesToProcess) {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
final VirtualFile vFile = psiFile.getVirtualFile();
|
||||
if (vFile == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (indicator != null) {
|
||||
indicator.setFraction((double)i / n);
|
||||
indicator.setText2(ProjectUtil.calcRelativeToProjectPath(vFile, project));
|
||||
}
|
||||
findAllStyleApplications(project, vFile, myAttrMap, myParentStyleNameAttrValue, usages);
|
||||
}
|
||||
return usages;
|
||||
}
|
||||
|
||||
private static void collectResDir(Module module, XmlAttributeValue styleNameAttrValue, String styleName, List<VirtualFile> resDirs) {
|
||||
final AndroidFacet f = AndroidFacet.getInstance(module);
|
||||
final VirtualFile resDir = f != null
|
||||
? f.getLocalResourceManager().getResourceDir()
|
||||
: null;
|
||||
if (resDir != null) {
|
||||
final List<ValueResourceInfoImpl> resolvedStyles = f.getLocalResourceManager().findValueResourceInfos(
|
||||
ResourceType.STYLE.getName(), styleName, true, false);
|
||||
|
||||
if (resolvedStyles.size() == 1) {
|
||||
final XmlAttributeValue resolvedStyleNameElement = resolvedStyles.get(0).computeXmlElement();
|
||||
|
||||
if (resolvedStyleNameElement != null &&
|
||||
resolvedStyleNameElement.equals(styleNameAttrValue)) {
|
||||
resDirs.add(resDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void filterFilesToScan(CacheManager cacheManager,
|
||||
String s,
|
||||
Set<PsiFile> result,
|
||||
GlobalSearchScope scope) {
|
||||
for (String word : StringUtil.getWordsInStringLongestFirst(s)) {
|
||||
final PsiFile[] files = cacheManager.getFilesWithWord(word, UsageSearchContext.ANY, scope, true);
|
||||
result.retainAll(Arrays.asList(files));
|
||||
}
|
||||
}
|
||||
|
||||
private static void findAllStyleApplications(final Project project,
|
||||
final VirtualFile layoutVFile,
|
||||
final Map<AndroidAttributeInfo, String> attrMap,
|
||||
final PsiElement parentStyleNameAttrValue,
|
||||
final List<UsageInfo> usages) {
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final PsiFile layoutFile = PsiManager.getInstance(project).findFile(layoutVFile);
|
||||
|
||||
if (!(layoutFile instanceof XmlFile)) {
|
||||
return;
|
||||
}
|
||||
if (!(DomManager.getDomManager(project).getDomFileDescription(
|
||||
(XmlFile)layoutFile) instanceof LayoutDomFileDescription)) {
|
||||
return;
|
||||
}
|
||||
layoutFile.accept(new XmlRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitXmlTag(XmlTag tag) {
|
||||
super.visitXmlTag(tag);
|
||||
|
||||
if (isPossibleApplicationOfStyle(project, tag, attrMap, parentStyleNameAttrValue)) {
|
||||
usages.add(new UsageInfo(tag));
|
||||
}
|
||||
}
|
||||
});
|
||||
PsiManager.getInstance(project).dropResolveCaches();
|
||||
InjectedLanguageManager.getInstance(project).dropFileCaches(layoutFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement getStyleNameAttrValueForTag(@NotNull LayoutViewElement element) {
|
||||
final AndroidResourceReferenceBase styleRef = AndroidDomUtil.
|
||||
getAndroidResourceReference(element.getStyle(), false);
|
||||
|
||||
if (styleRef != null) {
|
||||
final PsiElement[] styleElements = styleRef.computeTargetElements();
|
||||
|
||||
if (styleElements.length == 1) {
|
||||
return styleElements[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isPossibleApplicationOfStyle(Project project,
|
||||
XmlTag candidate,
|
||||
Map<AndroidAttributeInfo, String> attrMap,
|
||||
PsiElement parentStyleNameAttrValue) {
|
||||
final DomElement domCandidate = DomManager.getDomManager(project).getDomElement(candidate);
|
||||
|
||||
if (!(domCandidate instanceof LayoutViewElement)) {
|
||||
return false;
|
||||
}
|
||||
final LayoutViewElement candidateView = (LayoutViewElement)domCandidate;
|
||||
final Map<Pair<String, String>, String> attrsInCandidateMap = new HashMap<Pair<String, String>, String>();
|
||||
final List<XmlAttribute> attrsInCandidate = AndroidExtractStyleAction.getExtractableAttributes(candidate);
|
||||
|
||||
if (attrsInCandidate.size() < attrMap.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (XmlAttribute attribute : attrsInCandidate) {
|
||||
final String attrValue = attribute.getValue();
|
||||
|
||||
if (attrValue != null) {
|
||||
attrsInCandidateMap.put(Pair.create(attribute.getNamespace(), attribute.getLocalName()), attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<AndroidAttributeInfo, String> entry : attrMap.entrySet()) {
|
||||
final String ns = entry.getKey().getNamespace();
|
||||
final String name = entry.getKey().getName();
|
||||
final String value = entry.getValue();
|
||||
final String valueInCandidate = attrsInCandidateMap.get(Pair.create(ns, name));
|
||||
|
||||
if (valueInCandidate == null || !valueInCandidate.equals(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateView.getStyle().getStringValue() != null) {
|
||||
if (parentStyleNameAttrValue == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiElement styleNameAttrValueForTag = getStyleNameAttrValueForTag(candidateView);
|
||||
|
||||
if (styleNameAttrValueForTag == null ||
|
||||
!parentStyleNameAttrValue.equals(styleNameAttrValueForTag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (parentStyleNameAttrValue != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void setSearchOnlyInCurrentModule(boolean searchOnlyInCurrentModule) {
|
||||
mySearchOnlyInCurrentModule = searchOnlyInCurrentModule;
|
||||
}
|
||||
|
||||
public void setFileToScan(VirtualFile fileToScan) {
|
||||
myFileToScan = fileToScan;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Module getModule() {
|
||||
return myModule;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getStyleName() {
|
||||
return myStyleName;
|
||||
}
|
||||
|
||||
public void configureScope(MyScope scope, @Nullable VirtualFile context) {
|
||||
if (scope == MyScope.MODULE) {
|
||||
setSearchOnlyInCurrentModule(true);
|
||||
}
|
||||
else if (scope == MyScope.FILE) {
|
||||
setSearchOnlyInCurrentModule(true);
|
||||
setFileToScan(context);
|
||||
}
|
||||
}
|
||||
|
||||
enum MyScope {
|
||||
PROJECT, MODULE, FILE
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,19 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.DomManager;
|
||||
import com.intellij.util.xml.GenericAttributeValue;
|
||||
import org.jetbrains.android.dom.AndroidDomUtil;
|
||||
import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase;
|
||||
import org.jetbrains.android.dom.layout.Include;
|
||||
import org.jetbrains.android.dom.layout.LayoutViewElement;
|
||||
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.dom.wrappers.LazyValueResourceElementWrapper;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.android.util.ErrorReporter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -70,14 +67,14 @@ class AndroidInlineUtil {
|
||||
|
||||
if (domElement instanceof LayoutViewElement) {
|
||||
final GenericAttributeValue<ResourceValue> styleAttribute = ((LayoutViewElement)domElement).getStyle();
|
||||
final AndroidResourceReferenceBase reference = getAndroidResourceReference(styleAttribute);
|
||||
final AndroidResourceReferenceBase reference = AndroidDomUtil.getAndroidResourceReference(styleAttribute, true);
|
||||
|
||||
if (reference != null) {
|
||||
return new ViewStyleUsageData(tag, styleAttribute, reference);
|
||||
}
|
||||
}
|
||||
else if (domElement instanceof Style) {
|
||||
final AndroidResourceReferenceBase reference = getAndroidResourceReference(((Style)domElement).getParentStyle());
|
||||
final AndroidResourceReferenceBase reference = AndroidDomUtil.getAndroidResourceReference(((Style)domElement).getParentStyle(), true);
|
||||
|
||||
if (reference != null) {
|
||||
return new ParentStyleUsageData((Style)domElement, reference);
|
||||
@@ -93,7 +90,7 @@ class AndroidInlineUtil {
|
||||
|
||||
if (domElement instanceof Include) {
|
||||
final GenericAttributeValue<ResourceValue> layoutAttribute = ((Include)domElement).getLayout();
|
||||
final AndroidResourceReferenceBase reference = getAndroidResourceReference(layoutAttribute);
|
||||
final AndroidResourceReferenceBase reference = AndroidDomUtil.getAndroidResourceReference(layoutAttribute, true);
|
||||
|
||||
if (reference != null) {
|
||||
return new LayoutUsageData(project, tag, reference);
|
||||
@@ -102,74 +99,19 @@ class AndroidInlineUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AndroidResourceReferenceBase getAndroidResourceReference(@Nullable GenericAttributeValue<ResourceValue> attribute) {
|
||||
if (attribute == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ResourceValue resValue = attribute.getValue();
|
||||
if (resValue == null || resValue.getPackage() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final XmlAttributeValue value = attribute.getXmlAttributeValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (PsiReference reference : value.getReferences()) {
|
||||
if (reference instanceof AndroidResourceReferenceBase) {
|
||||
return (AndroidResourceReferenceBase)reference;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Map<AndroidAttributeInfo, String> computeAttributeMap(@NotNull Style style, @NotNull ErrorReporter errorReporter) {
|
||||
final Map<AndroidAttributeInfo, String> attributeValues = new HashMap<AndroidAttributeInfo, String>();
|
||||
|
||||
for (StyleItem item : style.getItems()) {
|
||||
final String attributeName = item.getName().getStringValue();
|
||||
String attributeValue = item.getStringValue();
|
||||
|
||||
if (attributeName == null || attributeName.length() <= 0 || attributeValue == null) {
|
||||
continue;
|
||||
}
|
||||
final int idx = attributeName.indexOf(':');
|
||||
final String localName = idx >= 0 ? attributeName.substring(idx + 1) : attributeName;
|
||||
final String nsPrefix = idx >= 0 ? attributeName.substring(0, idx) : null;
|
||||
|
||||
if (nsPrefix != null) {
|
||||
if (!AndroidUtils.SYSTEM_RESOURCE_PACKAGE.equals(nsPrefix)) {
|
||||
errorReporter.report(RefactoringBundle.getCannotRefactorMessage("Unknown XML attribute prefix '" + nsPrefix + ":'"),
|
||||
AndroidBundle.message("android.inline.style.title"));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
errorReporter.report(
|
||||
RefactoringBundle.getCannotRefactorMessage("The style contains attribute without 'android' prefix."),
|
||||
AndroidBundle.message("android.inline.style.title"));
|
||||
return null;
|
||||
}
|
||||
attributeValues.put(new AndroidAttributeInfo(localName, nsPrefix), attributeValue);
|
||||
}
|
||||
return attributeValues;
|
||||
}
|
||||
|
||||
static void doInlineStyleDeclaration(@NotNull Project project,
|
||||
@NotNull MyStyleData data,
|
||||
@Nullable final StyleUsageData usageData,
|
||||
@NotNull ErrorReporter errorReporter,
|
||||
@Nullable AndroidInlineTestConfig testConfig) {
|
||||
final Style style = data.myStyleElement;
|
||||
final Map<AndroidAttributeInfo, String> attributeValues = computeAttributeMap(style, errorReporter);
|
||||
final Map<AndroidAttributeInfo, String> attributeValues = AndroidRefactoringUtil.computeAttributeMap(style, errorReporter,
|
||||
AndroidBundle.message(
|
||||
"android.inline.style.title"));
|
||||
if (attributeValues == null) {
|
||||
return;
|
||||
}
|
||||
final StyleRefData parentStyleRef = getParentStyle(style);
|
||||
final StyleRefData parentStyleRef = AndroidRefactoringUtil.getParentStyle(style);
|
||||
boolean inlineThisOnly;
|
||||
|
||||
if (testConfig != null) {
|
||||
@@ -216,31 +158,6 @@ class AndroidInlineUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static StyleRefData getParentStyle(@NotNull Style style) {
|
||||
final ResourceValue parentStyleRefValue = style.getParentStyle().getValue();
|
||||
|
||||
if (parentStyleRefValue != null) {
|
||||
final String parentStyleName = parentStyleRefValue.getResourceName();
|
||||
|
||||
if (parentStyleName != null) {
|
||||
return new StyleRefData(parentStyleName, parentStyleRefValue.getPackage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String styleName = style.getName().getStringValue();
|
||||
|
||||
if (styleName != null) {
|
||||
final int idx = styleName.lastIndexOf('.');
|
||||
|
||||
if (idx > 0) {
|
||||
return new StyleRefData(styleName.substring(0, idx), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static MyStyleData getInlinableStyleDataFromContext(@Nullable PsiElement context) {
|
||||
if (context instanceof LazyValueResourceElementWrapper) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
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.util.AndroidUtils;
|
||||
import org.jetbrains.android.util.ErrorReporter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidRefactoringUtil {
|
||||
private AndroidRefactoringUtil() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static StyleRefData getParentStyle(@NotNull Style style) {
|
||||
final ResourceValue parentStyleRefValue = style.getParentStyle().getValue();
|
||||
|
||||
if (parentStyleRefValue != null) {
|
||||
final String parentStyleName = parentStyleRefValue.getResourceName();
|
||||
|
||||
if (parentStyleName != null) {
|
||||
return new StyleRefData(parentStyleName, parentStyleRefValue.getPackage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String styleName = style.getName().getStringValue();
|
||||
|
||||
if (styleName != null) {
|
||||
final int idx = styleName.lastIndexOf('.');
|
||||
|
||||
if (idx > 0) {
|
||||
return new StyleRefData(styleName.substring(0, idx), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Map<AndroidAttributeInfo, String> computeAttributeMap(@NotNull Style style,
|
||||
@NotNull ErrorReporter errorReporter,
|
||||
@NotNull String errorReportTitle) {
|
||||
final Map<AndroidAttributeInfo, String> attributeValues = new HashMap<AndroidAttributeInfo, String>();
|
||||
|
||||
for (StyleItem item : style.getItems()) {
|
||||
final String attributeName = item.getName().getStringValue();
|
||||
String attributeValue = item.getStringValue();
|
||||
|
||||
if (attributeName == null || attributeName.length() <= 0 || attributeValue == null) {
|
||||
continue;
|
||||
}
|
||||
final int idx = attributeName.indexOf(':');
|
||||
final String localName = idx >= 0 ? attributeName.substring(idx + 1) : attributeName;
|
||||
final String nsPrefix = idx >= 0 ? attributeName.substring(0, idx) : null;
|
||||
|
||||
if (nsPrefix != null) {
|
||||
if (!AndroidUtils.SYSTEM_RESOURCE_PACKAGE.equals(nsPrefix)) {
|
||||
errorReporter.report(RefactoringBundle.getCannotRefactorMessage("Unknown XML attribute prefix '" + nsPrefix + ":'"),
|
||||
errorReportTitle);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
errorReporter.report(
|
||||
RefactoringBundle.getCannotRefactorMessage("The style contains attribute without 'android' prefix."),
|
||||
errorReportTitle);
|
||||
return null;
|
||||
}
|
||||
attributeValues.put(new AndroidAttributeInfo(localName, nsPrefix), attributeValue);
|
||||
}
|
||||
return attributeValues;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" 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"/>
|
||||
<xy x="20" y="20" width="380" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@@ -27,7 +27,7 @@
|
||||
</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">
|
||||
<grid row="4" 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>
|
||||
@@ -37,7 +37,7 @@
|
||||
</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"/>
|
||||
<grid row="3" 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="&Attributes:"/>
|
||||
@@ -58,6 +58,14 @@
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="93b91" class="com.intellij.ui.components.JBCheckBox" binding="mySearchForStyleApplicationsAfter">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Launch 'Use Style Where Possible' refactoring after the style is extracted"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.module.Module;
|
||||
@@ -23,6 +24,7 @@ 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.JBCheckBox;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
@@ -52,6 +54,7 @@ class ExtractStyleDialog extends DialogWrapper {
|
||||
private JBLabel myAttributesLabel;
|
||||
private JBLabel myModuleLabel;
|
||||
private JComboBox myModuleCombo;
|
||||
private JBCheckBox mySearchForStyleApplicationsAfter;
|
||||
|
||||
private final Module myModule;
|
||||
private final String myFileName;
|
||||
@@ -60,6 +63,8 @@ class ExtractStyleDialog extends DialogWrapper {
|
||||
private final CheckboxTree myTree;
|
||||
private final CheckedTreeNode myRootNode;
|
||||
|
||||
private static final String SEARCH_STYLE_APPLICATIONS_PROPERTY = "AndroidExtractStyleSearchStyleApplications";
|
||||
|
||||
public ExtractStyleDialog(@NotNull Module module,
|
||||
@NotNull String fileName,
|
||||
@Nullable String parentStyleName,
|
||||
@@ -167,6 +172,9 @@ class ExtractStyleDialog extends DialogWrapper {
|
||||
decorator.addExtraAction(unselectAll);
|
||||
|
||||
myAttributeListWrapper.add(decorator.createPanel());
|
||||
|
||||
final String value = PropertiesComponent.getInstance().getValue(SEARCH_STYLE_APPLICATIONS_PROPERTY);
|
||||
mySearchForStyleApplicationsAfter.setSelected(Boolean.parseBoolean(value));
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -184,6 +192,13 @@ class ExtractStyleDialog extends DialogWrapper {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
super.doOKAction();
|
||||
PropertiesComponent.getInstance().setValue(
|
||||
SEARCH_STYLE_APPLICATIONS_PROPERTY, Boolean.toString(mySearchForStyleApplicationsAfter.isSelected()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myStyleNameField;
|
||||
@@ -230,4 +245,8 @@ class ExtractStyleDialog extends DialogWrapper {
|
||||
public Module getChosenModule() {
|
||||
return myModule != null ? myModule : (Module)myModuleCombo.getSelectedItem();
|
||||
}
|
||||
|
||||
public boolean isToSearchStyleApplications() {
|
||||
return mySearchForStyleApplicationsAfter.isSelected();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class ViewStyleUsageData implements StyleUsageData {
|
||||
for (Map.Entry<AndroidAttributeInfo, String> entry : attributeValues.entrySet()) {
|
||||
final AndroidAttributeInfo info = entry.getKey();
|
||||
final String localName = info.getName();
|
||||
final String namespace = info.isSystem() ? SdkConstants.NS_RESOURCES : null;
|
||||
final String namespace = info.getNamespace();
|
||||
|
||||
if (myTag.getAttribute(localName, namespace) == null) {
|
||||
myTag.setAttribute(localName, namespace, entry.getValue());
|
||||
|
||||
Reference in New Issue
Block a user