| /* |
| * 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.componentTree; |
| |
| import com.android.SdkConstants; |
| import com.android.tools.idea.rendering.IncludeReference; |
| import com.android.tools.lint.detector.api.LintUtils; |
| import com.intellij.android.designer.model.RadViewComponent; |
| import com.intellij.android.designer.model.ViewsMetaManager; |
| import com.intellij.codeHighlighting.HighlightDisplayLevel; |
| import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; |
| import com.intellij.designer.componentTree.AttributeWrapper; |
| import com.intellij.designer.componentTree.TreeComponentDecorator; |
| import com.intellij.designer.model.*; |
| import com.intellij.designer.palette.PaletteItem; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.ui.LayeredIcon; |
| import com.intellij.ui.SimpleColoredComponent; |
| import com.intellij.ui.SimpleTextAttributes; |
| import icons.AndroidIcons; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| |
| import static com.android.SdkConstants.*; |
| import static com.android.tools.idea.rendering.IncludeReference.ATTR_RENDER_IN; |
| import static com.intellij.android.designer.model.RadModelBuilder.ROOT_NODE_TAG; |
| |
| /** |
| * Tree decorator for the component tree in Android. |
| * <p> |
| * It displays the id (if any) in bold, the tag name (unless implied by the id), |
| * and the key attributes of the view in gray. It also inlines {@code <view>} tags. |
| * Finally, it uses the palette icons, and overlays warning and error icons |
| * if the corresponding tag has lint warnings. |
| */ |
| public final class AndroidTreeDecorator implements TreeComponentDecorator { |
| @Nullable private final Project myProject; |
| |
| public AndroidTreeDecorator(@Nullable Project project) { |
| myProject = project; |
| } |
| |
| @Override |
| public void decorate(RadComponent component, SimpleColoredComponent renderer, AttributeWrapper wrapper, boolean full) { |
| MetaModel metaModel = component.getMetaModel(); |
| |
| // Special case: for the <view> tag, show the referenced |
| // class instead |
| String tag = metaModel.getTag(); |
| if (VIEW_TAG.equals(tag) && component instanceof RadViewComponent) { |
| // We have to use the XmlTag to look up the class attribute since the |
| // component.getPropertyValue(ATTR_CLASS) call does not return it |
| RadViewComponent rvc = (RadViewComponent)component; |
| XmlAttribute attribute = rvc.getTag().getAttribute(ATTR_CLASS); |
| if (attribute != null) { |
| String cls = attribute.getValue(); |
| if (!StringUtil.isEmpty(cls)) { |
| if (myProject != null) { |
| MetaManager metaManager = ViewsMetaManager.getInstance(myProject); |
| MetaModel classModel = metaManager.getModelByTarget(cls); |
| if (classModel != null) { |
| metaModel = classModel; |
| } |
| } |
| } |
| } |
| } |
| decorate(component, metaModel, renderer, wrapper, full); |
| } |
| |
| private void decorate(RadComponent component, |
| MetaModel metaModel, |
| SimpleColoredComponent renderer, |
| AttributeWrapper wrapper, |
| boolean full) { |
| String id = component.getPropertyValue(ATTR_ID); |
| id = LintUtils.stripIdPrefix(id); |
| id = StringUtil.nullize(id); |
| |
| PaletteItem item = metaModel.getPaletteItem(); |
| String type = null; |
| String tagName = metaModel.getTag(); |
| if (item != null) { |
| type = item.getTitle(); |
| |
| // Don't display <Fragment> etc for special XML tags like <requestFocus> |
| if (tagName.equals(VIEW_INCLUDE) || |
| tagName.equals(VIEW_MERGE) || |
| tagName.equals(VIEW_FRAGMENT) || |
| tagName.equals(REQUEST_FOCUS)) { |
| type = null; |
| } |
| } |
| |
| if (id != null) { |
| SimpleTextAttributes idStyle = wrapper.getAttribute(SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); |
| renderer.append(id, idStyle); |
| } |
| |
| if (id == null && type == null) { |
| type = tagName; |
| } |
| |
| // For the root node, show the including layout when rendering in included contexts |
| if (ROOT_NODE_TAG.equals(tagName)) { |
| IncludeReference includeContext = component.getClientProperty(ATTR_RENDER_IN); |
| if (includeContext != null && includeContext != IncludeReference.NONE) { |
| type = "Shown in " + includeContext.getFromResourceUrl(); |
| } |
| } |
| |
| // Don't display the type if it's obvious from the id (e.g. |
| // if the id is button1, don't display (Button) as the type) |
| if (type != null && (id == null || !StringUtil.startsWithIgnoreCase(id, type))) { |
| SimpleTextAttributes typeStyle = wrapper.getAttribute(SimpleTextAttributes.REGULAR_ATTRIBUTES); |
| renderer.append(id != null ? String.format(" (%1$s)", type) : type, typeStyle); |
| } |
| |
| // Display typical arguments |
| StringBuilder fullTitle = new StringBuilder(); |
| String title = metaModel.getTitle(); |
| if (title != null) { |
| int start = title.indexOf('%'); |
| if (start != -1) { |
| int end = title.indexOf('%', start + 1); |
| if (end != -1) { |
| String variable = title.substring(start + 1, end); |
| |
| String value = component.getPropertyValue(variable); |
| if (!StringUtil.isEmpty(value)) { |
| value = StringUtil.shortenTextWithEllipsis(value, 30, 5); |
| } |
| |
| if (!StringUtil.isEmpty(value)) { |
| String prefix = title.substring(0, start); |
| String suffix = title.substring(end + 1); |
| if ((value.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || value.startsWith(SdkConstants.PREFIX_THEME_REF)) |
| && prefix.length() > 0 && suffix.length() > 0 && |
| prefix.charAt(prefix.length() - 1) == '"' && |
| suffix.charAt(0) == '"') { |
| // If the value is a resource, don't surround it with quotes |
| prefix = prefix.substring(0, prefix.length() - 1); |
| suffix = suffix.substring(1); |
| } |
| fullTitle.append(prefix).append(value).append(suffix); |
| } |
| } |
| } |
| } |
| |
| if (fullTitle.length() > 0) { |
| SimpleTextAttributes valueStyle = wrapper.getAttribute(SimpleTextAttributes.GRAY_ATTRIBUTES); |
| renderer.append(fullTitle.toString(), valueStyle); |
| } |
| |
| if (full) { |
| Icon icon = metaModel.getIcon(); |
| |
| // Annotate icons with lint warnings or errors, if applicable |
| HighlightDisplayLevel displayLevel = null; |
| if (myProject != null) { |
| SeverityRegistrar severityRegistrar = SeverityRegistrar.getSeverityRegistrar(myProject); |
| for (ErrorInfo errorInfo : RadComponent.getError(component)) { |
| if (displayLevel == null || severityRegistrar.compare(errorInfo.getLevel().getSeverity(), displayLevel.getSeverity()) > 0) { |
| displayLevel = errorInfo.getLevel(); |
| } |
| } |
| if (displayLevel == HighlightDisplayLevel.ERROR) { |
| LayeredIcon layeredIcon = new LayeredIcon(2); |
| layeredIcon.setIcon(icon, 0); |
| layeredIcon.setIcon(AndroidIcons.ErrorBadge, 1, 10, 10); |
| icon = layeredIcon; |
| } else if (displayLevel == HighlightDisplayLevel.WARNING || displayLevel == HighlightDisplayLevel.WEAK_WARNING) { |
| LayeredIcon layeredIcon = new LayeredIcon(2); |
| layeredIcon.setIcon(icon, 0); |
| layeredIcon.setIcon(AndroidIcons.WarningBadge, 1, 10, 10); |
| icon = layeredIcon; |
| } |
| } |
| |
| renderer.setIcon(icon); |
| |
| if (component instanceof IComponentDecorator) { |
| ((IComponentDecorator)component).decorateTree(renderer, wrapper); |
| } |
| } |
| } |
| } |