From 8f36645feda3908db4a6abd21ea330984e88988a Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Wed, 1 Aug 2012 14:05:38 +0400 Subject: [PATCH] IDEA-70314 android extract style refactoring --- .../propertyTable/editors/ResourceDialog.java | 56 +-- .../messages/AndroidBundle.properties | 3 +- plugins/android/src/META-INF/plugin.xml | 4 + .../jetbrains/android/dom/AndroidDomUtil.java | 13 + .../android/dom/resources/Style.java | 2 + .../AndroidExtractStyleAction.java | 358 +++++++++++++ .../refactoring/ExtractStyleDialog.form | 63 +++ .../refactoring}/ExtractStyleDialog.java | 471 +++++++++--------- .../android/util/AndroidResourceUtil.java | 71 +-- .../jetbrains/android/util/AndroidUtils.java | 20 +- .../testData/refactoring/extractStyle/1.xml | 7 + .../refactoring/extractStyle/1_after.xml | 5 + .../extractStyle/1_styles_after.xml | 6 + .../testData/refactoring/extractStyle/2.xml | 8 + .../refactoring/extractStyle/2_after.xml | 6 + .../refactoring/extractStyle/2_styles.xml | 5 + .../extractStyle/2_styles_after.xml | 9 + .../testData/refactoring/extractStyle/3.xml | 8 + .../refactoring/extractStyle/3_after.xml | 6 + .../refactoring/extractStyle/3_styles.xml | 5 + .../extractStyle/3_styles_after.xml | 9 + .../testData/refactoring/extractStyle/4.xml | 8 + .../refactoring/extractStyle/4_after.xml | 6 + .../extractStyle/4_styles_after.xml | 6 + .../testData/refactoring/extractStyle/5.xml | 6 + .../testData/refactoring/extractStyle/6.xml | 8 + .../testData/refactoring/extractStyle/7.xml | 7 + .../refactoring/extractStyle/7_after.xml | 5 + .../extractStyle/7_styles_after.xml | 6 + .../testData/refactoring/extractStyle/8.xml | 3 + .../refactoring/AndroidExtractStyleTest.java | 123 +++++ 31 files changed, 989 insertions(+), 324 deletions(-) create mode 100644 plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java create mode 100644 plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.form rename plugins/{android-designer/src/com/intellij/android/designer/propertyTable/editors => android/src/org/jetbrains/android/refactoring}/ExtractStyleDialog.java (57%) create mode 100644 plugins/android/testData/refactoring/extractStyle/1.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/1_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/1_styles_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/2.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/2_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/2_styles.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/2_styles_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/3.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/3_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/3_styles.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/3_styles_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/4.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/4_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/4_styles_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/5.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/6.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/7.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/7_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/7_styles_after.xml create mode 100644 plugins/android/testData/refactoring/extractStyle/8.xml create mode 100644 plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidExtractStyleTest.java diff --git a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java b/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java index 5e0e3e9606f9..026e1d036a6b 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java +++ b/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java @@ -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 subDirs = AndroidResourceUtil.getResourceSubdirs(ResourceFolderType.VALUES.getName(), dirs); - List dirNames = new ArrayList(); - 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 attributes = dialog.getStyledAttributes(); - - StringBuilder value = new StringBuilder(); - for (XmlAttribute attribute : attributes) { - value.append("").append(attribute.getValue()).append("\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); } diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index b6f65eec3731..e9073840a220 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -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}'' \ No newline at end of file +invalid.resource.name.error=Invalid resource name ''{0}'' +android.extract.style.title=Extract Android Style \ No newline at end of file diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index a08133a02f03..287282e4fda2 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -82,6 +82,10 @@ icon="/icons/attachDebugger.png"> + + + JUnit diff --git a/plugins/android/src/org/jetbrains/android/dom/AndroidDomUtil.java b/plugins/android/src/org/jetbrains/android/dom/AndroidDomUtil.java index d6c22d63499c..b121358700e3 100644 --- a/plugins/android/src/org/jetbrains/android/dom/AndroidDomUtil.java +++ b/plugins/android/src/org/jetbrains/android/dom/AndroidDomUtil.java @@ -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 SPECIAL_RESOURCE_TYPES = new HashMap(); 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"); diff --git a/plugins/android/src/org/jetbrains/android/dom/resources/Style.java b/plugins/android/src/org/jetbrains/android/dom/resources/Style.java index c3e99bf0b482..012613e6f7f1 100644 --- a/plugins/android/src/org/jetbrains/android/dom/resources/Style.java +++ b/plugins/android/src/org/jetbrains/android/dom/resources/Style.java @@ -33,6 +33,8 @@ import java.util.List; public interface Style extends ResourceElement { List getItems(); + StyleItem addItem(); + @Attribute("parent") @Convert(ParentStyleConverter.class) GenericAttributeValue getParentStyle(); diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java new file mode 100644 index 000000000000..5449ea7d1c30 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java @@ -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 dirNames = Arrays.asList(ResourceFolderType.VALUES.getName()); + final List 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 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 attrsToExtract = new HashSet(Arrays.asList(testConfig.getAttributesToExtract())); + styledAttributes = new ArrayList(); + + 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 attributesToDelete = new ArrayList(); + + if (!AndroidResourceUtil + .createValueResource(chosenModule, styleName, ResourceType.STYLE, fileName, dirNames, new Processor() { + @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 getExtractableAttributes(@NotNull XmlTag viewTag) { + final List extractableAttributes = new ArrayList(); + + 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 extractableAttributes) { + } + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.form b/plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.form new file mode 100644 index 000000000000..56a25ee114b9 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.form @@ -0,0 +1,63 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ExtractStyleDialog.java b/plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.java similarity index 57% rename from plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ExtractStyleDialog.java rename to plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.java index 19a3e7abf678..ed4eb8ff2e14 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ExtractStyleDialog.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/ExtractStyleDialog.java @@ -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 myDirNames; - - private final JPanel myPanel; - private final JTextField myNameText; - private final CheckboxTree myTree; - - private final CheckedTreeNode myRootNode; - - public ExtractStyleDialog(Module module, String fileName, List 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() { - 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 getStyledAttributes() { - List attributes = new ArrayList(); - - 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 myDirNames; + + private final CheckboxTree myTree; + private final CheckedTreeNode myRootNode; + + public ExtractStyleDialog(@NotNull Module module, + @NotNull String fileName, + @Nullable String parentStyleName, + @NotNull List dirNames, + @NotNull List attributes) { + super(module.getProject()); + myFileName = fileName; + myDirNames = dirNames; + + if (parentStyleName != null && parentStyleName.length() > 0) { + myStyleNameField.setText(parentStyleName + "."); + myStyleNameField.selectAll(); + } + + final Set modulesSet = new HashSet(); + 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() { + @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() { + 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 getStyledAttributes() { + List attributes = new ArrayList(); + 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(); + } } \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java b/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java index 15ce866737bc..5925d4bf3488 100644 --- a/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java +++ b/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java @@ -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 dirNames, - @NotNull String value) { + @NotNull String resourceName, + @NotNull final ResourceType resourceType, + @NotNull String fileName, + @NotNull List dirNames, + @NotNull Processor 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 dirNames, + @NotNull final String value) { + return createValueResource(module, resourceName, resourceType, fileName, dirNames, new Processor() { + @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 dirNames, - @NotNull final String value) throws Exception { + @NotNull final Processor 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 getReferredResourceField(@NotNull AndroidFacet facet, @NotNull PsiReferenceExpression exp) { final String resFieldName = exp.getReferenceName(); diff --git a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java index 82207c492b93..04033be5faba 100644 --- a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java +++ b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java @@ -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); + } + } + } diff --git a/plugins/android/testData/refactoring/extractStyle/1.xml b/plugins/android/testData/refactoring/extractStyle/1.xml new file mode 100644 index 000000000000..9c210d21c1ef --- /dev/null +++ b/plugins/android/testData/refactoring/extractStyle/1.xml @@ -0,0 +1,7 @@ + + on android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@android:color/black" + android:background="@android:color/white" + android:text="Hello"/> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractStyle/1_after.xml b/plugins/android/testData/refactoring/extractStyle/1_after.xml new file mode 100644 index 000000000000..11ddbdb34bcb --- /dev/null +++ b/plugins/android/testData/refactoring/extractStyle/1_after.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractStyle/7.xml b/plugins/android/testData/refactoring/extractStyle/7.xml new file mode 100644 index 000000000000..49bdbe078278 --- /dev/null +++ b/plugins/android/testData/refactoring/extractStyle/7.xml @@ -0,0 +1,7 @@ + +