Add gutter icons for Android resource references
#KT-16843 Fixed
This commit is contained in:
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.AbstractDataFlowValueRenderingTest
|
||||
import org.jetbrains.kotlin.addImport.AbstractAddImportTest
|
||||
import org.jetbrains.kotlin.allopen.AbstractBytecodeListingTestForAllOpen
|
||||
import org.jetbrains.kotlin.android.*
|
||||
import org.jetbrains.kotlin.android.annotator.AbstractAndroidGutterIconTest
|
||||
import org.jetbrains.kotlin.android.configure.AbstractConfigureProjectTest
|
||||
import org.jetbrains.kotlin.android.folding.AbstractAndroidResourceFoldingTest
|
||||
import org.jetbrains.kotlin.android.intention.AbstractAndroidIntentionTest
|
||||
@@ -1260,6 +1261,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractAndroidResourceFoldingTest> {
|
||||
model("android/folding")
|
||||
}
|
||||
|
||||
testClass<AbstractAndroidGutterIconTest> {
|
||||
model("android/gutterIcon")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("plugins/plugins-tests/tests", "plugins/android-extensions/android-extensions-jps/testData") {
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.kotlin.android
|
||||
|
||||
import com.android.SdkConstants
|
||||
import com.android.resources.ResourceType.*
|
||||
import com.android.tools.idea.AndroidPsiUtils.ResourceReferenceType.FRAMEWORK
|
||||
import com.android.tools.idea.rendering.GutterIconRenderer
|
||||
import com.android.tools.idea.res.ResourceHelper
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.android.AndroidColorAnnotator
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.android.ResourceReferenceAnnotatorUtil.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
|
||||
class AndroidResourceReferenceAnnotator : Annotator {
|
||||
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
|
||||
val reference = element as? KtReferenceExpression ?: return
|
||||
val androidFacet = AndroidFacet.getInstance(element) ?: return
|
||||
val referenceTarget = reference.getResourceReferenceTargetDescriptor() ?: return
|
||||
val resourceType = referenceTarget.getAndroidResourceType() ?: return
|
||||
|
||||
if (resourceType != COLOR && resourceType != DRAWABLE && resourceType != MIPMAP) {
|
||||
return
|
||||
}
|
||||
|
||||
val referenceType = referenceTarget.getResourceReferenceType()
|
||||
val configuration = pickConfiguration(androidFacet, androidFacet.module, element.containingFile) ?: return
|
||||
val resourceValue = findResourceValue(resourceType,
|
||||
reference.text,
|
||||
referenceType == FRAMEWORK,
|
||||
androidFacet.module,
|
||||
configuration) ?: return
|
||||
|
||||
val resourceResolver = configuration.resourceResolver ?: return
|
||||
|
||||
if (resourceType == COLOR) {
|
||||
val color = ResourceHelper.resolveColor(resourceResolver, resourceValue, element.project)
|
||||
if (color != null) {
|
||||
val annotation = holder.createInfoAnnotation(element, null)
|
||||
annotation.gutterIconRenderer = ColorRenderer(element, color)
|
||||
}
|
||||
}
|
||||
else {
|
||||
var file = ResourceHelper.resolveDrawable(resourceResolver, resourceValue, element.project)
|
||||
if (file != null && file.path.endsWith(SdkConstants.DOT_XML)) {
|
||||
file = pickBitmapFromXml(file, resourceResolver, element.project)
|
||||
}
|
||||
val iconFile = AndroidColorAnnotator.pickBestBitmap(file)
|
||||
if (iconFile != null) {
|
||||
val annotation = holder.createInfoAnnotation(element, null)
|
||||
annotation.gutterIconRenderer = GutterIconRenderer(element, iconFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtReferenceExpression.getResourceReferenceTargetDescriptor(): JavaPropertyDescriptor? =
|
||||
analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, this] as? JavaPropertyDescriptor
|
||||
}
|
||||
@@ -17,6 +17,11 @@
|
||||
package org.jetbrains.kotlin.android
|
||||
|
||||
import com.android.SdkConstants
|
||||
import com.android.SdkConstants.ANDROID_PKG
|
||||
import com.android.SdkConstants.R_CLASS
|
||||
import com.android.resources.ResourceType
|
||||
import com.android.tools.idea.AndroidPsiUtils
|
||||
import com.android.tools.idea.AndroidPsiUtils.ResourceReferenceType.*
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.android.augment.AndroidPsiElementFinder
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
@@ -25,13 +30,17 @@ import org.jetbrains.android.util.AndroidResourceUtil.isManifestJavaFile
|
||||
import org.jetbrains.android.util.AndroidResourceUtil.isRJavaFile
|
||||
import org.jetbrains.android.util.AndroidUtils
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
|
||||
|
||||
@@ -40,49 +49,72 @@ internal fun PsiElement.getAndroidFacetForFile(): AndroidFacet? {
|
||||
return AndroidFacet.getInstance(file)
|
||||
}
|
||||
|
||||
internal object KotlinAndroidResourceUtil {
|
||||
fun getReferredResourceOrManifestField(facet: AndroidFacet, expression: KtSimpleNameExpression, localOnly: Boolean)
|
||||
= getReferredResourceOrManifestField(facet, expression, null, localOnly)
|
||||
|
||||
fun getReferredResourceOrManifestField(facet: AndroidFacet, expression: KtSimpleNameExpression,
|
||||
className: String?, localOnly: Boolean): AndroidResourceUtil.MyReferredResourceFieldInfo? {
|
||||
val resFieldName = expression.getReferencedName()
|
||||
val resClassReference = expression.getPreviousInQualifiedChain() as? KtSimpleNameExpression ?: return null
|
||||
val resClassName = resClassReference.getReferencedName()
|
||||
|
||||
if (resClassName.isEmpty() || className != null && className != resClassName) {
|
||||
return null
|
||||
}
|
||||
|
||||
val rClassReference = resClassReference.getPreviousInQualifiedChain() as? KtSimpleNameExpression ?: return null
|
||||
val rClassDescriptor = rClassReference.analyze(BodyResolveMode.PARTIAL)
|
||||
.get(BindingContext.REFERENCE_TARGET, rClassReference) as? ClassDescriptor ?: return null
|
||||
|
||||
val rClassShortName = rClassDescriptor.name.asString()
|
||||
val fromManifest = AndroidUtils.MANIFEST_CLASS_NAME == rClassShortName
|
||||
|
||||
if (!fromManifest && AndroidUtils.R_CLASS_NAME != rClassShortName) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!localOnly) {
|
||||
val qName = rClassDescriptor.fqNameSafe.asString()
|
||||
|
||||
if (SdkConstants.CLASS_R == qName || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME == qName) {
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
val containingFile = (rClassDescriptor.source.containingFile as? PsiSourceFile)?.psiFile ?: return null
|
||||
if (if (fromManifest) !isManifestJavaFile(facet, containingFile) else !isRJavaFile(facet, containingFile)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, false, false)
|
||||
internal fun JavaPropertyDescriptor.getAndroidResourceType(): ResourceType? {
|
||||
if (getResourceReferenceType() == NONE) {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun KtExpression.getPreviousInQualifiedChain(): KtExpression? {
|
||||
val receiverExpression = getQualifiedExpressionForSelector()?.receiverExpression
|
||||
return (receiverExpression as? KtQualifiedExpression)?.selectorExpression ?: receiverExpression
|
||||
val containingClass = containingDeclaration as? JavaClassDescriptor ?: return null
|
||||
return ResourceType.getEnum(containingClass.name.asString())
|
||||
}
|
||||
|
||||
internal fun JavaPropertyDescriptor.getResourceReferenceType(): AndroidPsiUtils.ResourceReferenceType {
|
||||
val containingClass = containingDeclaration as? JavaClassDescriptor ?: return NONE
|
||||
val rClass = containingClass.containingDeclaration as? JavaClassDescriptor ?: return NONE
|
||||
|
||||
if (R_CLASS == rClass.name.asString()) {
|
||||
if ((rClass.containingDeclaration as? PackageFragmentDescriptor)?.fqName?.asString() == ANDROID_PKG) {
|
||||
return FRAMEWORK
|
||||
}
|
||||
else {
|
||||
return APP
|
||||
}
|
||||
}
|
||||
|
||||
return NONE
|
||||
}
|
||||
|
||||
internal fun getReferredResourceOrManifestField(facet: AndroidFacet, expression: KtSimpleNameExpression, localOnly: Boolean)
|
||||
= getReferredResourceOrManifestField(facet, expression, null, localOnly)
|
||||
|
||||
internal fun getReferredResourceOrManifestField(facet: AndroidFacet, expression: KtSimpleNameExpression,
|
||||
className: String?, localOnly: Boolean): AndroidResourceUtil.MyReferredResourceFieldInfo? {
|
||||
val resFieldName = expression.getReferencedName()
|
||||
val resClassReference = expression.getPreviousInQualifiedChain() as? KtSimpleNameExpression ?: return null
|
||||
val resClassName = resClassReference.getReferencedName()
|
||||
|
||||
if (resClassName.isEmpty() || className != null && className != resClassName) {
|
||||
return null
|
||||
}
|
||||
|
||||
val rClassReference = resClassReference.getPreviousInQualifiedChain() as? KtSimpleNameExpression ?: return null
|
||||
val rClassDescriptor = rClassReference.analyze(BodyResolveMode.PARTIAL)
|
||||
.get(BindingContext.REFERENCE_TARGET, rClassReference) as? ClassDescriptor ?: return null
|
||||
|
||||
val rClassShortName = rClassDescriptor.name.asString()
|
||||
val fromManifest = AndroidUtils.MANIFEST_CLASS_NAME == rClassShortName
|
||||
|
||||
if (!fromManifest && AndroidUtils.R_CLASS_NAME != rClassShortName) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!localOnly) {
|
||||
val qName = rClassDescriptor.fqNameSafe.asString()
|
||||
|
||||
if (SdkConstants.CLASS_R == qName || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME == qName) {
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
val containingFile = (rClassDescriptor.source.containingFile as? PsiSourceFile)?.psiFile ?: return null
|
||||
if (if (fromManifest) !isManifestJavaFile(facet, containingFile) else !isRJavaFile(facet, containingFile)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, false, false)
|
||||
}
|
||||
|
||||
private fun KtExpression.getPreviousInQualifiedChain(): KtExpression? {
|
||||
val receiverExpression = getQualifiedExpressionForSelector()?.receiverExpression
|
||||
return (receiverExpression as? KtQualifiedExpression)?.selectorExpression ?: receiverExpression
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.kotlin.android;
|
||||
|
||||
|
||||
import com.android.ide.common.rendering.api.ResourceValue;
|
||||
import com.android.ide.common.resources.ResourceItem;
|
||||
import com.android.ide.common.resources.ResourceRepository;
|
||||
import com.android.ide.common.resources.ResourceResolver;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.tools.idea.configurations.Configuration;
|
||||
import com.android.tools.idea.res.AppResourceRepository;
|
||||
import com.android.tools.idea.res.LocalResourceRepository;
|
||||
import com.android.tools.idea.res.ResourceHelper;
|
||||
import com.android.tools.idea.ui.resourcechooser.ColorPicker;
|
||||
import com.android.utils.XmlUtils;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
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.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ui.ColorIcon;
|
||||
import com.intellij.util.ui.EmptyIcon;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
import static com.android.SdkConstants.*;
|
||||
import static com.android.SdkConstants.ANDROID_URI;
|
||||
import static com.android.SdkConstants.ATTR_DRAWABLE;
|
||||
import static com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer.ICON_SIZE;
|
||||
import static org.jetbrains.android.AndroidColorAnnotator.pickLayoutFile;
|
||||
|
||||
/**
|
||||
* Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator
|
||||
*/
|
||||
public class ResourceReferenceAnnotatorUtil {
|
||||
@Nullable
|
||||
public static File pickBitmapFromXml(@NotNull File file, @NotNull ResourceResolver resourceResolver, @NotNull Project project) {
|
||||
try {
|
||||
String xml = Files.toString(file, Charsets.UTF_8);
|
||||
Document document = XmlUtils.parseDocumentSilently(xml, true);
|
||||
if (document != null && document.getDocumentElement() != null) {
|
||||
Element root = document.getDocumentElement();
|
||||
String tag = root.getTagName();
|
||||
Element target = null;
|
||||
String attribute = null;
|
||||
if ("vector".equals(tag)) {
|
||||
// Vectors are handled in the icon cache
|
||||
return file;
|
||||
}
|
||||
else if ("bitmap".equals(tag) || "nine-patch".equals(tag)) {
|
||||
target = root;
|
||||
attribute = ATTR_SRC;
|
||||
}
|
||||
else if ("selector".equals(tag) ||
|
||||
"level-list".equals(tag) ||
|
||||
"layer-list".equals(tag) ||
|
||||
"transition".equals(tag)) {
|
||||
NodeList children = root.getChildNodes();
|
||||
for (int i = children.getLength() - 1; i >= 0; i--) {
|
||||
Node item = children.item(i);
|
||||
if (item.getNodeType() == Node.ELEMENT_NODE && TAG_ITEM.equals(item.getNodeName())) {
|
||||
target = (Element)item;
|
||||
if (target.hasAttributeNS(ANDROID_URI, ATTR_DRAWABLE)) {
|
||||
attribute = ATTR_DRAWABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("clip".equals(tag) || "inset".equals(tag) || "scale".equals(tag)) {
|
||||
target = root;
|
||||
attribute = ATTR_DRAWABLE;
|
||||
} else {
|
||||
// <shape> etc - no bitmap to be found
|
||||
return null;
|
||||
}
|
||||
if (attribute != null && target.hasAttributeNS(ANDROID_URI, attribute)) {
|
||||
String src = target.getAttributeNS(ANDROID_URI, attribute);
|
||||
ResourceValue value = resourceResolver.findResValue(src, false);
|
||||
if (value != null) {
|
||||
return ResourceHelper.resolveDrawable(resourceResolver, value, project);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
// Not logging for now; afraid to risk unexpected crashes in upcoming preview. TODO: Re-enable.
|
||||
//Logger.getInstance(AndroidColorAnnotator.class).warn(String.format("Could not read/render icon image %1$s", file), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Looks up the resource item of the given type and name for the given configuration, if any */
|
||||
@Nullable
|
||||
public static ResourceValue findResourceValue(ResourceType type,
|
||||
String name,
|
||||
boolean isFramework,
|
||||
Module module,
|
||||
Configuration configuration) {
|
||||
if (isFramework) {
|
||||
ResourceRepository frameworkResources = configuration.getFrameworkResources();
|
||||
if (frameworkResources == null) {
|
||||
return null;
|
||||
}
|
||||
if (!frameworkResources.hasResourceItem(type, name)) {
|
||||
return null;
|
||||
}
|
||||
ResourceItem item = frameworkResources.getResourceItem(type, name);
|
||||
return item.getResourceValue(type, configuration.getFullConfig(), false);
|
||||
} else {
|
||||
LocalResourceRepository appResources = AppResourceRepository.getAppResources(module, true);
|
||||
if (appResources == null) {
|
||||
return null;
|
||||
}
|
||||
if (!appResources.hasResourceItem(type, name)) {
|
||||
return null;
|
||||
}
|
||||
return appResources.getConfiguredValue(type, name, configuration.getFullConfig());
|
||||
}
|
||||
}
|
||||
|
||||
/** Picks a suitable configuration to use for resource resolution */
|
||||
@Nullable
|
||||
public static Configuration pickConfiguration(AndroidFacet facet, Module module, PsiFile file) {
|
||||
VirtualFile virtualFile = file.getVirtualFile();
|
||||
if (virtualFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VirtualFile parent = virtualFile.getParent();
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile layout;
|
||||
String parentName = parent.getName();
|
||||
if (!parentName.startsWith(FD_RES_LAYOUT)) {
|
||||
layout = pickLayoutFile(module, facet);
|
||||
if (layout == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
layout = virtualFile;
|
||||
}
|
||||
|
||||
return facet.getConfigurationManager().getConfiguration(layout);
|
||||
}
|
||||
|
||||
public static class ColorRenderer extends GutterIconRenderer {
|
||||
private final PsiElement myElement;
|
||||
private final Color myColor;
|
||||
|
||||
ColorRenderer(@NotNull PsiElement element, @Nullable Color color) {
|
||||
myElement = element;
|
||||
myColor = color;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
Color color = getCurrentColor();
|
||||
return JBUI.scale(color == null ? EmptyIcon.create(ICON_SIZE) : new ColorIcon(ICON_SIZE, color));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Color getCurrentColor() {
|
||||
if (myColor != null) {
|
||||
return myColor;
|
||||
} else if (myElement instanceof XmlTag) {
|
||||
return ResourceHelper.parseColor(((XmlTag)myElement).getValue().getText());
|
||||
} else if (myElement instanceof XmlAttributeValue) {
|
||||
return ResourceHelper.parseColor(((XmlAttributeValue)myElement).getValue());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnAction getClickAction() {
|
||||
if (myColor != null) { // Cannot set colors that were derived
|
||||
return null;
|
||||
}
|
||||
return new AnAction() {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
|
||||
if (editor != null) {
|
||||
// Need ARGB support in platform color chooser; see
|
||||
// https://youtrack.jetbrains.com/issue/IDEA-123498
|
||||
//final Color color =
|
||||
// ColorChooser.chooseColor(editor.getComponent(), AndroidBundle.message("android.choose.color"), getCurrentColor());
|
||||
final Color color = ColorPicker.showDialog(editor.getComponent(), "Choose Color", getCurrentColor(), true, null, false);
|
||||
if (color != null) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (myElement instanceof XmlTag) {
|
||||
((XmlTag)myElement).getValue().setText(ResourceHelper.colorToString(color));
|
||||
} else if (myElement instanceof XmlAttributeValue) {
|
||||
XmlAttribute attribute = PsiTreeUtil.getParentOfType(myElement, XmlAttribute.class);
|
||||
if (attribute != null) {
|
||||
attribute.setValue(ResourceHelper.colorToString(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ColorRenderer that = (ColorRenderer)o;
|
||||
// TODO: Compare with modification count in app resources (if not framework)
|
||||
if (myColor != null ? !myColor.equals(that.myColor) : that.myColor != null) return false;
|
||||
if (!myElement.equals(that.myElement)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myElement.hashCode();
|
||||
result = 31 * result + (myColor != null ? myColor.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -25,7 +25,7 @@ import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.android.inspections.CreateFileResourceQuickFix
|
||||
import org.jetbrains.android.inspections.CreateValueResourceQuickFix
|
||||
import org.jetbrains.android.util.AndroidResourceUtil
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidResourceUtil
|
||||
import org.jetbrains.kotlin.android.getReferredResourceOrManifestField
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class KotlinAndroidResourceQuickFixProvider : UnresolvedReferenceQuickFixProvide
|
||||
manifest.`package`.value ?: return
|
||||
val contextFile = expression.containingFile ?: return
|
||||
|
||||
val info = KotlinAndroidResourceUtil.getReferredResourceOrManifestField(facet, expression, true)
|
||||
val info = getReferredResourceOrManifestField(facet, expression, true)
|
||||
if (info == null || info.isFromManifest) {
|
||||
return
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.kotlin.android.annotator
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.util.ui.ColorIcon
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.awt.Color
|
||||
import java.io.File
|
||||
import javax.swing.ImageIcon
|
||||
|
||||
|
||||
abstract class AbstractAndroidGutterIconTest : KotlinAndroidTestCase() {
|
||||
|
||||
fun doTest(path: String) {
|
||||
val testFile = File(path)
|
||||
val testFileText = FileUtil.loadFile(testFile)
|
||||
val withRuntime = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// WITH_RUNTIME")
|
||||
|
||||
val drawable = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// DRAWABLE")
|
||||
val color = InTextDirectivesUtils.findListWithPrefixes(testFileText, "// COLOR: ").takeIf { it.isNotEmpty() }?.let {
|
||||
val components = it.map { it.toInt(16) }
|
||||
Color(components[0], components[1], components[2])
|
||||
}
|
||||
|
||||
try {
|
||||
if (withRuntime) {
|
||||
ConfigLibraryUtil.configureKotlinRuntime(myFixture.module)
|
||||
}
|
||||
|
||||
copyResourceDirectoryForTest(path)
|
||||
myFixture.copyFileToProject(testFile.parent + "/R.java", "gen/${COM_MYAPP_PACKAGE_PATH}R.java")
|
||||
|
||||
val sourceFile = myFixture.copyFileToProject(path, "src/${PathUtil.getFileName(path)}")
|
||||
myFixture.configureFromExistingVirtualFile(sourceFile)
|
||||
|
||||
val gutter = myFixture.findAllGutters().find {
|
||||
when {
|
||||
drawable -> it.icon is ImageIcon
|
||||
color != null -> (it.icon as? ColorIcon)?.iconColor == color
|
||||
else -> error("COLOR or DRAWABLE must be defined!")
|
||||
}
|
||||
}
|
||||
|
||||
TestCase.assertNotNull(gutter)
|
||||
}
|
||||
finally {
|
||||
if (withRuntime) {
|
||||
ConfigLibraryUtil.unConfigureKotlinRuntime(myFixture.module)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COM_MYAPP_PACKAGE_PATH: String = "com/myapp/"
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.kotlin.android.annotator;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TargetBackend;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/android/gutterIcon")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class AndroidGutterIconTestGenerated extends AbstractAndroidGutterIconTest {
|
||||
public void testAllFilesPresentInGutterIcon() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/gutterIcon"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("color.kt")
|
||||
public void testColor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/gutterIcon/color.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("drawable.kt")
|
||||
public void testDrawable() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/gutterIcon/drawable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("mipmap.kt")
|
||||
public void testMipmap() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/gutterIcon/mipmap.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("systemColor.kt")
|
||||
public void testSystemColor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/gutterIcon/systemColor.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("systemDrawable.kt")
|
||||
public void testSystemDrawable() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/gutterIcon/systemDrawable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/gutterIcon/res")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Res extends AbstractAndroidGutterIconTest {
|
||||
public void testAllFilesPresentInRes() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/gutterIcon/res"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,8 @@
|
||||
|
||||
<lang.foldingBuilder language="kotlin" implementationClass="org.jetbrains.kotlin.android.folding.ResourceFoldingBuilder" />
|
||||
|
||||
<annotator language="kotlin" implementationClass="org.jetbrains.kotlin.android.AndroidResourceReferenceAnnotator" />
|
||||
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.myapp;
|
||||
|
||||
public final class R {
|
||||
public static final class drawable {
|
||||
public static final int test_icon=0x7f02005d;
|
||||
}
|
||||
public static final class mipmap {
|
||||
public static final int test_icon=0x7f04005d;
|
||||
}
|
||||
public static final class color {
|
||||
public static final int colorAccent=0x7f0a0013;
|
||||
public static final int colorPrimary=0x7f0a0014;
|
||||
public static final int colorPrimaryDark=0x7f0a0015;
|
||||
}
|
||||
public static final class layout {
|
||||
public static final int activity_kotlin=0x7f08005d;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// COLOR: FF,40,81
|
||||
package com.myapp
|
||||
|
||||
import android.content.Context
|
||||
|
||||
<caret>fun Context.getPrimaryColor() = resources.getColor(R.color.colorAccent, null)
|
||||
@@ -0,0 +1,6 @@
|
||||
// DRAWABLE
|
||||
package com.myapp
|
||||
|
||||
import android.content.Context
|
||||
|
||||
<caret>fun Context.getAddIcon() = resources.getDrawable(R.drawable.test_icon, null)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DRAWABLE
|
||||
package com.myapp
|
||||
|
||||
import android.content.Context
|
||||
|
||||
<caret>fun Context.getAddIcon() = resources.getDrawable(R.mipmap.test_icon, null)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_kotlin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
</RelativeLayout>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
// COLOR: 0,0,0
|
||||
import android.content.Context
|
||||
|
||||
<caret>fun getBlackColor() = android.R.color.black
|
||||
@@ -0,0 +1,4 @@
|
||||
// DRAWABLE
|
||||
import android.content.Context
|
||||
|
||||
fun <caret>Context.getAddIcon() = resources.getDrawable(android.R.drawable.ic_menu_add, null)
|
||||
Reference in New Issue
Block a user