| /* |
| * 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); |
| } |
| }); |
| } |
| } |