blob: 2f7166d4bb474f8409f82dd6471c3b4a14dfde17 [file] [log] [blame]
/*
* 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.model.layout.grid;
import com.android.ide.common.rendering.api.ViewInfo;
import com.intellij.android.designer.model.RadComponentOperations;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.android.designer.model.RadViewContainer;
import com.intellij.android.designer.model.grid.GridInfo;
import com.intellij.android.designer.model.grid.IGridProvider;
import com.intellij.designer.componentTree.AttributeWrapper;
import com.intellij.designer.model.IComponentDecorator;
import com.intellij.designer.model.RadComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import org.jetbrains.android.inspections.lint.SuppressLintIntentionAction;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.Arrays;
import static com.android.SdkConstants.*;
import static org.jetbrains.android.inspections.lint.SuppressLintIntentionAction.ensureNamespaceImported;
/**
* @author Alexander Lobas
*/
public class RadGridLayoutComponent extends RadViewContainer implements IComponentDecorator, IGridProvider {
private GridInfo myGridInfo;
private GridInfo myVirtualGridInfo;
/**
* Returns the namespace URI to use for GridLayout-specific attributes, such
* as columnCount, layout_column, layout_column_span, layout_gravity etc.
*
* @param component the component instance to look up the namespace for; typically
* the grid layout itself (to look up attributes like columnCount)
* or one of its children (to look up the layout parameters)
* @return the namespace, never null
*/
@NotNull
public static String getGridLayoutNamespace(@NotNull RadViewComponent component) {
RadComponent parent = component.getParent();
if (parent instanceof RadViewComponent) {
String fqcn = ((RadViewComponent)parent).getTag().getName();
if (fqcn.equals(FQCN_GRID_LAYOUT_V7)) {
return AUTO_URI;
}
}
String fqcn = component.getTag().getName();
if (fqcn.equals(FQCN_GRID_LAYOUT_V7)) {
return AUTO_URI;
}
return ANDROID_URI;
}
@Override
public void decorateTree(SimpleColoredComponent renderer, AttributeWrapper wrapper) {
XmlTag tag = getTag();
StringBuilder value = new StringBuilder(" (");
String namespace = getGridLayoutNamespace(this);
String rowCount = tag.getAttributeValue(ATTR_ROW_COUNT, namespace);
value.append(StringUtil.isEmpty(rowCount) ? "?" : rowCount).append(", ");
String columnCount = tag.getAttributeValue(ATTR_COLUMN_COUNT, namespace);
value.append(StringUtil.isEmpty(columnCount) ? "?" : columnCount).append(", ");
value.append(isHorizontal() ? VALUE_HORIZONTAL : VALUE_VERTICAL);
renderer.append(value.append(")").toString(), wrapper.getAttribute(SimpleTextAttributes.REGULAR_ATTRIBUTES));
}
public boolean isHorizontal() {
return !"vertical".equals(getTag().getAttributeValue(ATTR_ORIENTATION, getGridLayoutNamespace(this)));
}
@Override
public void setViewInfo(ViewInfo viewInfo) {
super.setViewInfo(viewInfo);
myGridInfo = null;
myVirtualGridInfo = null;
}
@Override
public GridInfo getGridInfo() {
if (myGridInfo == null) {
myGridInfo = new GridInfo(this);
try {
Object viewObject = myViewInfo.getViewObject();
Class<?> viewClass = viewObject.getClass();
myGridInfo.rowCount = (Integer)viewClass.getMethod("getRowCount").invoke(viewObject);
myGridInfo.columnCount = (Integer)viewClass.getMethod("getColumnCount").invoke(viewObject);
// Field names changed in KitKat
String verticalAxisName = "verticalAxis";
Field field_horizontalAxis;
try {
field_horizontalAxis = viewClass.getDeclaredField("horizontalAxis");
} catch (NoSuchFieldException e) {
field_horizontalAxis = viewClass.getDeclaredField("mHorizontalAxis");
verticalAxisName = "mVerticalAxis";
}
field_horizontalAxis.setAccessible(true);
Object horizontalAxis = field_horizontalAxis.get(viewObject);
Class<?> class_Axis = horizontalAxis.getClass();
Field field_locations = class_Axis.getField("locations");
field_locations.setAccessible(true);
myGridInfo.vLines = (int[])field_locations.get(horizontalAxis);
myGridInfo.emptyColumns = configureEmptyLines(myGridInfo.vLines);
Field field_verticalAxis = viewClass.getDeclaredField(verticalAxisName);
field_verticalAxis.setAccessible(true);
Object verticalAxis = field_verticalAxis.get(viewObject);
myGridInfo.hLines = (int[])field_locations.get(verticalAxis);
myGridInfo.emptyRows = configureEmptyLines(myGridInfo.hLines);
Rectangle bounds = getBounds();
for (RadComponent child : getChildren()) {
Rectangle childBounds = child.getBounds();
myGridInfo.width = Math.max(myGridInfo.width, childBounds.x + childBounds.width - bounds.x);
myGridInfo.height = Math.max(myGridInfo.height, childBounds.y + childBounds.height - bounds.y);
}
if (myGridInfo.vLines != null && myGridInfo.vLines.length > 0) {
myGridInfo.vLines[myGridInfo.vLines.length - 1] = myGridInfo.width;
}
if (myGridInfo.hLines != null && myGridInfo.hLines.length > 0) {
myGridInfo.hLines[myGridInfo.hLines.length - 1] = myGridInfo.height;
}
}
catch (Throwable e) {
}
}
return myGridInfo;
}
private static final int EMPTY_CELL = 5;
private static boolean[] configureEmptyLines(int[] lines) {
boolean[] empty = new boolean[lines.length - 1];
int[] originalLines = Arrays.copyOf(lines, lines.length);
for (int i = 0; i < empty.length; i++) {
int line_i = originalLines[i];
int length = originalLines[i + 1] - line_i;
empty[i] = length == 0;
if (length == 0) {
int startMove = i + 1;
while (startMove < lines.length && line_i == lines[startMove]) {
startMove++;
}
for (int j = i + 1; j < lines.length; j++) {
lines[j] += EMPTY_CELL;
}
for (int j = startMove; j < lines.length; j++) {
if (lines[j - 1] < lines[j] - 2 * EMPTY_CELL) {
lines[j] -= 2 * EMPTY_CELL;
}
}
}
}
return empty;
}
@Override
public GridInfo getVirtualGridInfo() {
if (myVirtualGridInfo == null) {
myVirtualGridInfo = new GridInfo(this);
GridInfo gridInfo = getGridInfo();
Rectangle bounds = getBounds();
myVirtualGridInfo.rowCount = gridInfo.rowCount;
myVirtualGridInfo.columnCount = gridInfo.columnCount;
myVirtualGridInfo.width = bounds.width;
myVirtualGridInfo.height = bounds.height;
int deltaWidth = bounds.width - gridInfo.width;
myVirtualGridInfo.vLines = GridInfo.addLineInfo(gridInfo.vLines, deltaWidth);
if (deltaWidth < 2) {
myVirtualGridInfo.lastInsertColumn = gridInfo.columnCount - 1;
}
int deltaHeight = bounds.height - gridInfo.height;
myVirtualGridInfo.hLines = GridInfo.addLineInfo(gridInfo.hLines, deltaHeight);
if (deltaHeight < 2) {
myVirtualGridInfo.lastInsertRow = gridInfo.rowCount - 1;
}
myVirtualGridInfo.components = getGridComponents(true);
}
return myVirtualGridInfo;
}
public RadComponent[][] getGridComponents(boolean fillSpans) {
GridInfo gridInfo = getGridInfo();
RadComponent[][] components = new RadComponent[gridInfo.rowCount][gridInfo.columnCount];
for (RadComponent child : getChildren()) {
Rectangle cellInfo = getCellInfo(child);
if (fillSpans) {
int rowEnd = Math.min(cellInfo.y + cellInfo.height, gridInfo.rowCount);
int columnEnd = Math.min(cellInfo.x + cellInfo.width, gridInfo.columnCount);
for (int row = cellInfo.y; row < rowEnd; row++) {
for (int column = cellInfo.x; column < columnEnd; column++) {
components[row][column] = child;
}
}
}
else if (cellInfo.y < gridInfo.rowCount && cellInfo.x < gridInfo.columnCount) {
components[cellInfo.y][cellInfo.x] = child;
}
}
return components;
}
public static Rectangle getCellInfo(RadComponent component) {
Rectangle cellInfo = new Rectangle();
try {
Object layoutParams = ((RadViewComponent)component).getViewInfo().getLayoutParamsObject();
Class<?> layoutParamsClass = layoutParams.getClass();
Object columnSpec = layoutParamsClass.getField("columnSpec").get(layoutParams);
Object rowSpec = layoutParamsClass.getField("rowSpec").get(layoutParams);
Class<?> class_Spec = columnSpec.getClass();
Field field_span = class_Spec.getDeclaredField("span");
field_span.setAccessible(true);
Object columnSpan = field_span.get(columnSpec);
Object rowSpan = field_span.get(rowSpec);
Class<?> class_Interval = columnSpan.getClass();
Field field_min = class_Interval.getField("min");
field_min.setAccessible(true);
Field field_max = class_Interval.getField("max");
field_max.setAccessible(true);
cellInfo.x = field_min.getInt(columnSpan);
cellInfo.y = field_min.getInt(rowSpan);
cellInfo.width = field_max.getInt(columnSpan) - cellInfo.x;
cellInfo.height = field_max.getInt(rowSpan) - cellInfo.y;
}
catch (Throwable e) {
}
return cellInfo;
}
public static void setCellIndex(final RadComponent component,
final int row,
final int column,
final boolean clearRowSpan,
final boolean clearColumnSpan) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
XmlTag tag = ((RadViewComponent)component).getTag();
String namespace = getGridLayoutNamespace((RadViewComponent)component);
ensureNamespaceImported(tag.getProject(), (XmlFile)tag.getContainingFile(), namespace);
tag.setAttribute(ATTR_LAYOUT_ROW, namespace, Integer.toString(row));
tag.setAttribute(ATTR_LAYOUT_COLUMN, namespace, Integer.toString(column));
if (clearRowSpan) {
RadComponentOperations.deleteAttribute(tag, ATTR_LAYOUT_ROW_SPAN, namespace);
}
if (clearColumnSpan) {
RadComponentOperations.deleteAttribute(tag, ATTR_LAYOUT_COLUMN_SPAN, namespace);
}
XmlTag layoutTag = ((RadViewComponent)component.getParent()).getTag();
String columnCount = layoutTag.getAttributeValue(ATTR_COLUMN_COUNT, namespace);
if (columnCount != null) {
int columns = Integer.parseInt(columnCount);
int requiredColumns = column + (clearColumnSpan ? 1 : getSpan(component, false));
if (requiredColumns > columns) {
layoutTag.setAttribute(ATTR_COLUMN_COUNT, namespace, Integer.toString(requiredColumns));
}
}
String rowCount = layoutTag.getAttributeValue(ATTR_ROW_COUNT, namespace);
if (rowCount != null) {
int rows = Integer.parseInt(rowCount);
int requiredRows = row + (clearRowSpan ? 1 : getSpan(component, true));
if (requiredRows > rows) {
layoutTag.setAttribute(ATTR_ROW_COUNT, namespace, Integer.toString(requiredRows));
}
}
}
});
}
public static int getSpan(RadComponent component, boolean row) {
try {
String namespace = getGridLayoutNamespace((RadViewComponent)component);
String span =
((RadViewComponent)component).getTag().getAttributeValue(row ? ATTR_LAYOUT_ROW_SPAN : ATTR_LAYOUT_COLUMN_SPAN, namespace);
return Integer.parseInt(span);
}
catch (Throwable e) {
return 1;
}
}
public static void setSpan(final RadComponent component, final int span, final boolean row) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
String namespace = getGridLayoutNamespace((RadViewComponent)component);
XmlTag tag = ((RadViewComponent)component).getTag();
ensureNamespaceImported(tag.getProject(), (XmlFile)tag.getContainingFile(), namespace);
tag.setAttribute(row ? ATTR_LAYOUT_ROW_SPAN : ATTR_LAYOUT_COLUMN_SPAN, namespace, Integer.toString(span));
}
});
}
public static void clearCellSpans(final RadComponent component) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
String namespace = getGridLayoutNamespace((RadViewComponent)component);
XmlTag tag = ((RadViewComponent)component).getTag();
RadComponentOperations.deleteAttribute(tag, ATTR_LAYOUT_ROW_SPAN, namespace);
RadComponentOperations.deleteAttribute(tag, ATTR_LAYOUT_COLUMN_SPAN, namespace);
}
});
}
}