| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * 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.android.tools.idea.rendering; |
| |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.ide.common.rendering.api.StyleResourceValue; |
| import com.android.ide.common.resources.ResourceResolver; |
| import com.google.common.collect.Lists; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiElement; |
| 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 org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collection; |
| import java.util.List; |
| |
| import static com.android.SdkConstants.*; |
| |
| public class AddMissingAttributesFix extends WriteCommandAction<Void> { |
| @NotNull private final XmlFile myFile; |
| @Nullable private final ResourceResolver myResourceResolver; |
| |
| public AddMissingAttributesFix(@NotNull Project project, @NotNull XmlFile file, @Nullable ResourceResolver resourceResolver) { |
| super(project, "Add Size Attributes", file); |
| myFile = file; |
| myResourceResolver = resourceResolver; |
| } |
| |
| @NotNull |
| public List<XmlTag> findViewsMissingSizes() { |
| final List<XmlTag> missing = Lists.newArrayList(); |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| Collection<XmlTag> xmlTags = PsiTreeUtil.findChildrenOfType(myFile, XmlTag.class); |
| for (XmlTag tag : xmlTags) { |
| if (requiresSize(tag)) { |
| if (!definesWidth(tag, myResourceResolver) || !definesHeight(tag, myResourceResolver)) { |
| missing.add(tag); |
| } |
| } |
| } |
| } |
| }); |
| |
| return missing; |
| } |
| |
| |
| @Override |
| protected void run(Result<Void> result) throws Throwable { |
| final List<XmlTag> missing = findViewsMissingSizes(); |
| for (XmlTag tag : missing) { |
| if (!definesWidth(tag, myResourceResolver)) { |
| tag.setAttribute(ATTR_LAYOUT_WIDTH, ANDROID_URI, getDefaultWidth(tag)); |
| } |
| if (!definesHeight(tag, myResourceResolver)) { |
| tag.setAttribute(ATTR_LAYOUT_HEIGHT, ANDROID_URI, getDefaultHeight(tag)); |
| } |
| } |
| } |
| |
| public static boolean definesHeight(@NotNull XmlTag tag, @Nullable ResourceResolver resourceResolver) { |
| XmlAttribute height = tag.getAttribute(ATTR_LAYOUT_HEIGHT, ANDROID_URI); |
| boolean definesHeight = height != null; |
| |
| if (definesHeight) { |
| String value = height.getValue(); |
| if (value == null || value.isEmpty()) { |
| return false; |
| } |
| return value.equals(VALUE_WRAP_CONTENT) || value.equals(VALUE_FILL_PARENT) || value.equals(VALUE_MATCH_PARENT) || |
| value.startsWith(PREFIX_RESOURCE_REF) || value.startsWith(PREFIX_THEME_REF) || Character.isDigit(value.charAt(0)); |
| } else if (resourceResolver != null) { |
| String style = tag.getAttributeValue(ATTR_STYLE); |
| if (style != null) { |
| ResourceValue st = resourceResolver.findResValue(style, false); |
| if (st instanceof StyleResourceValue) { |
| StyleResourceValue styleValue = (StyleResourceValue)st; |
| definesHeight = resourceResolver.findItemInStyle(styleValue, ATTR_LAYOUT_HEIGHT, true) != null; |
| } |
| } |
| } |
| |
| return definesHeight; |
| } |
| |
| public static boolean definesWidth(@NotNull XmlTag tag, @Nullable ResourceResolver resourceResolver) { |
| XmlAttribute width = tag.getAttribute(ATTR_LAYOUT_WIDTH, ANDROID_URI); |
| boolean definesWidth = width != null; |
| |
| if (definesWidth) { |
| String value = width.getValue(); |
| if (value == null || value.isEmpty()) { |
| return false; |
| } |
| return value.equals(VALUE_WRAP_CONTENT) || value.equals(VALUE_FILL_PARENT) || value.equals(VALUE_MATCH_PARENT) || |
| value.startsWith(PREFIX_RESOURCE_REF) || value.startsWith(PREFIX_THEME_REF) || Character.isDigit(value.charAt(0)); |
| } else if (resourceResolver != null) { |
| String style = tag.getAttributeValue(ATTR_STYLE); |
| if (style != null) { |
| ResourceValue st = resourceResolver.findResValue(style, false); |
| if (st instanceof StyleResourceValue) { |
| StyleResourceValue styleValue = (StyleResourceValue)st; |
| definesWidth = resourceResolver.findItemInStyle(styleValue, ATTR_LAYOUT_WIDTH, true) != null; |
| } |
| } |
| } |
| |
| return definesWidth; |
| } |
| |
| @NotNull |
| private static String getDefaultWidth(@NotNull XmlTag tag) { |
| // Depends on parent and child. For now, just do wrap unless it's a layout |
| //String tagName = tag.getName(); |
| |
| // See Change-Id: I335a3bd8e2d7f7866692898ed73492635a5b61ea |
| // For the platform layouts the default value is WRAP_CONTENT (and is |
| // defined in the ViewGroup.LayoutParams class). The special cases |
| // are accommodated in LayoutParams subclasses in the following cases: |
| // Subclass width height |
| // FrameLayout.LayoutParams: MATCH_PARENT, MATCH_PARENT |
| // TableLayout.LayoutParams: MATCH_PARENT, WRAP_CONTENT |
| // TableRow.LayoutParams: MATCH_PARENT, WRAP_CONTENT |
| XmlTag parentTag = getParentTag(tag); |
| if (parentTag != null) { |
| String parent = parentTag.getName(); |
| if (parent.equals(FRAME_LAYOUT) || parent.equals(TABLE_LAYOUT) || parent.equals(TABLE_ROW)) { |
| return VALUE_MATCH_PARENT; // TODO: VALUE_FILL_PARENT? |
| } |
| // TODO: If custom view, check its parentage! |
| } |
| |
| return VALUE_WRAP_CONTENT; |
| } |
| |
| @NotNull |
| private static String getDefaultHeight(@NotNull XmlTag tag) { |
| // See #getDefaultWidth for a description of the defaults |
| XmlTag parentTag = getParentTag(tag); |
| if (parentTag != null) { |
| String parent = parentTag.getName(); |
| if (parent.equals(FRAME_LAYOUT) ) { |
| return VALUE_MATCH_PARENT; |
| } |
| } |
| |
| return VALUE_WRAP_CONTENT; |
| } |
| |
| @Nullable |
| private static XmlTag getParentTag(@NotNull XmlTag tag) { |
| PsiElement parent = tag.getParent(); |
| if (parent instanceof XmlTag) { |
| return (XmlTag)parent; |
| } |
| |
| return null; |
| } |
| |
| private static boolean requiresSize(XmlTag tag) { |
| XmlTag parentTag = getParentTag(tag); |
| if (parentTag != null) { |
| String parentName = parentTag.getName(); |
| if (GRID_LAYOUT.equals(parentName) || FQCN_GRID_LAYOUT_V7.equals(parentName)) { |
| return false; |
| } |
| } |
| |
| String tagName = tag.getName(); |
| if (tagName.equals(REQUEST_FOCUS) || tagName.equals(VIEW_MERGE) || tagName.equals(VIEW_INCLUDE)) { |
| return false; |
| } |
| |
| return true; |
| } |
| } |