TableLayout

This commit is contained in:
Alexander Lobas
2012-04-17 22:04:32 +04:00
parent 24f40ef0ad
commit d1c25a19d7
11 changed files with 481 additions and 107 deletions
@@ -26,14 +26,21 @@ import java.awt.*;
* @author Alexander Lobas
*/
public class CaptionStaticDecorator extends StaticDecorator {
private final Color myColor;
public CaptionStaticDecorator(RadComponent component) {
this(component, LightColors.GREEN);
}
public CaptionStaticDecorator(RadComponent component, Color color) {
super(component);
myColor = color;
}
@Override
protected void paint(DecorationLayer layer, Graphics2D g, RadComponent component) {
Rectangle bounds = component.getBounds(layer);
g.setColor(LightColors.GREEN);
g.setColor(myColor);
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
g.setColor(Color.black);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
@@ -15,14 +15,13 @@
*/
package com.intellij.android.designer.designSurface.layout;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.android.designer.model.table.GridInfo;
import com.intellij.android.designer.model.table.RadTableLayoutComponent;
import com.intellij.designer.designSurface.DecorationLayer;
import com.intellij.designer.designSurface.StaticDecorator;
import com.intellij.designer.model.RadComponent;
import java.awt.*;
import java.util.List;
/**
* @author Alexander Lobas
@@ -39,42 +38,17 @@ public class TableLayoutDecorator extends StaticDecorator {
g.setStroke(FlowStaticDecorator.STROKE);
Rectangle bounds = component.getBounds(layer);
int[] columnWidths = ((RadTableLayoutComponent)component).getColumnWidths();
GridInfo gridInfo = ((RadTableLayoutComponent)component).getGridInfo();
if (columnWidths != null && columnWidths.length > 0) {
int x = bounds.x;
for (int i = 0; i < columnWidths.length; i++) {
int width = columnWidths[i];
if (width > 0) {
x += width;
if (i != columnWidths.length - 1 || x < bounds.getMinX()) {
g.drawLine(x, bounds.y, x, bounds.y + bounds.height);
}
}
else {
g.drawLine(x + 2, bounds.y, x + 2, bounds.y + bounds.height);
}
}
bounds.width = Math.max(bounds.width, x - bounds.x);
for (int x : gridInfo.vLines) {
g.drawLine(bounds.x + x, bounds.y, bounds.x + x, bounds.y + gridInfo.height);
}
List<RadComponent> children = component.getChildren();
RadComponent last = children.isEmpty() ? null : children.get(children.size() - 1);
for (RadComponent child : children) {
Rectangle childBounds = child.getBounds(layer);
int y = childBounds.y + childBounds.height + ((RadViewComponent)child).getMargins().height;
if (child != last || y < bounds.getMaxY()) {
g.drawLine(bounds.x, y, bounds.x + bounds.width, y);
}
for (int y : gridInfo.hLines) {
g.drawLine(bounds.x, bounds.y + y, bounds.x + gridInfo.width, bounds.y + y);
}
g.setStroke(stroke);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
g.drawRect(bounds.x, bounds.y, gridInfo.width, gridInfo.height);
g.drawRect(bounds.x + 1, bounds.y + 1, gridInfo.width - 2, gridInfo.height - 2);
}
}
@@ -16,39 +16,57 @@
package com.intellij.android.designer.designSurface.layout;
import com.intellij.android.designer.designSurface.AbstractEditOperation;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.android.designer.model.table.GridInfo;
import com.intellij.android.designer.model.table.GridInsertType;
import com.intellij.android.designer.model.table.RadTableLayoutComponent;
import com.intellij.designer.designSurface.FeedbackLayer;
import com.intellij.designer.designSurface.OperationContext;
import com.intellij.designer.designSurface.feedbacks.AlphaFeedback;
import com.intellij.designer.designSurface.feedbacks.LineInsertFeedback;
import com.intellij.designer.model.RadComponent;
import javax.swing.*;
import java.awt.*;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class TableLayoutOperation extends AbstractEditOperation {
private GridFeedback myFeedback;
private LineInsertFeedback myHInsertFeedback;
private LineInsertFeedback myVInsertFeedback;
private int myColumn;
private int myRow;
private GridInsertType myInsertType;
private boolean myExist;
public TableLayoutOperation(RadComponent container, OperationContext context) {
super(container, context);
}
private void createFeedback() {
if (myFeedback == null) {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
myFeedback = new GridFeedback();
myFeedback.setBounds(myContainer.getBounds(layer));
layer.add(myFeedback);
layer.repaint();
}
private GridInfo getGridInfo() {
return ((RadTableLayoutComponent)myContainer).getVirtualGridInfo();
}
@Override
public void showFeedback() {
createFeedback();
if (myFeedback == null) {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
myFeedback = new GridFeedback();
myFeedback.setBounds(myContainer.getBounds(layer));
myHInsertFeedback = new LineInsertFeedback(Color.green, true);
myVInsertFeedback = new LineInsertFeedback(Color.green, false);
layer.add(myFeedback);
layer.add(myHInsertFeedback);
layer.add(myVInsertFeedback);
layer.repaint();
}
calculateGridInfo();
myFeedback.repaint();
}
@Override
@@ -56,64 +74,192 @@ public class TableLayoutOperation extends AbstractEditOperation {
if (myFeedback != null) {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
layer.remove(myFeedback);
layer.add(myHInsertFeedback);
layer.add(myVInsertFeedback);
layer.repaint();
myFeedback = null;
myHInsertFeedback = null;
myVInsertFeedback = null;
}
}
private void calculateGridInfo() {
GridInfo gridInfo = getGridInfo();
Point location = SwingUtilities.convertPoint(myContext.getArea().getFeedbackLayer(), myContext.getLocation(), myFeedback);
myColumn = getLineIndex(gridInfo.vLines, location.x);
myRow = getLineIndex(gridInfo.hLines, location.y);
RadComponent[][] components = gridInfo.components;
if (components == null) {
myInsertType = GridInsertType.in_cell;
myExist = false;
}
else {
myInsertType = null;
Rectangle bounds = myContainer.getBounds(myContext.getArea().getFeedbackLayer());
Rectangle cellRect = getInsertRect(myExist);
Rectangle inCellRect = getInsertInRect(cellRect);
if (inCellRect.contains(location)) {
myInsertType = GridInsertType.in_cell;
myHInsertFeedback.setVisible(false);
myVInsertFeedback.setVisible(false);
}
else if (location.x < inCellRect.x) {
myInsertType = GridInsertType.before_v_cell;
myHInsertFeedback.setVisible(false);
myVInsertFeedback.setLocation(bounds.x + cellRect.x, bounds.y + cellRect.y);
myVInsertFeedback.setSize(0, cellRect.height);
myVInsertFeedback.setVisible(true);
}
else if (location.x > inCellRect.x) {
myInsertType = GridInsertType.after_v_cell;
myHInsertFeedback.setVisible(false);
myVInsertFeedback.setLocation(bounds.x + cellRect.x + cellRect.width, bounds.y + cellRect.y);
myVInsertFeedback.setSize(0, cellRect.height);
myVInsertFeedback.setVisible(true);
}
else if (location.y < inCellRect.y) {
myInsertType = GridInsertType.before_h_cell;
myHInsertFeedback.setVisible(true);
myHInsertFeedback.setLocation(bounds.x + cellRect.x, bounds.y + cellRect.y);
myHInsertFeedback.setSize(cellRect.width, 0);
myVInsertFeedback.setVisible(false);
}
else if (location.y > inCellRect.y) {
myInsertType = GridInsertType.after_h_cell;
myHInsertFeedback.setVisible(true);
myHInsertFeedback.setLocation(bounds.x + cellRect.x, bounds.y + cellRect.y + cellRect.height);
myHInsertFeedback.setSize(cellRect.width, 0);
myVInsertFeedback.setVisible(false);
}
if (myInsertType == GridInsertType.in_cell) {
if (myRow < components.length && myColumn < components[0].length) {
myExist = components[myRow][myColumn] != null;
}
else {
myExist = false;
}
}
}
}
private static int getLineIndex(int[] line, int location) {
for (int i = 0; i < line.length - 1; i++) {
if (line[i] <= location && location <= line[i + 1]) {
return i;
}
}
return Math.max(0, line.length - 1);
}
private Rectangle getInsertRect(boolean includeSpan) {
GridInfo gridInfo = getGridInfo();
int startColumn = myColumn;
int endColumn = myColumn + 1;
if (includeSpan) {
RadComponent[] columnComponents = gridInfo.components[myRow];
RadComponent existComponent = columnComponents[startColumn];
while (startColumn > 0) {
if (columnComponents[startColumn - 1] == existComponent) {
startColumn--;
}
else {
break;
}
}
while (endColumn < columnComponents.length) {
if (columnComponents[endColumn] == existComponent) {
endColumn++;
}
else {
break;
}
}
}
int x1 = startColumn < gridInfo.vLines.length ? gridInfo.vLines[startColumn] : 0;
int x2 = endColumn < gridInfo.vLines.length ? gridInfo.vLines[endColumn] : gridInfo.width;
int y1 = myRow < gridInfo.hLines.length ? gridInfo.hLines[myRow] : 0;
int y2 = myRow + 1 < gridInfo.hLines.length ? gridInfo.hLines[myRow + 1] : gridInfo.height;
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
private static Rectangle getInsertInRect(Rectangle cellRect) {
int borderWidth = getInsertBorderLength(cellRect.width);
int borderHeight = getInsertBorderLength(cellRect.height);
return new Rectangle(cellRect.x + borderWidth, cellRect.y + borderHeight, cellRect.width - 2 * borderWidth,
cellRect.height - 2 * borderHeight);
}
private static int getInsertBorderLength(int length) {
int border = 5;
if (length < 3 * border) {
border = length / 3;
}
return border;
}
@Override
public boolean canExecute() {
return !myExist;
}
@Override
public void execute() throws Exception {
super.execute(); // TODO: Auto-generated method stub
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// Feedback
//
//////////////////////////////////////////////////////////////////////////////////////////
public static final Color COLOR = new Color(80, 110, 160);
private class GridFeedback extends JComponent {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(COLOR);
g.setColor(BorderStaticDecorator.COLOR);
Rectangle bounds = getBounds();
bounds.x = bounds.y = 0;
GridInfo gridInfo = getGridInfo();
int[] columnWidths = ((RadTableLayoutComponent)myContainer).getColumnWidths();
if (columnWidths != null && columnWidths.length > 0) {
int x = bounds.x;
for (int i = 0; i < columnWidths.length; i++) {
int width = columnWidths[i];
if (width > 0) {
x += width;
if (i != columnWidths.length - 1 || x < bounds.getMinX()) {
g.drawLine(x, bounds.y, x, bounds.y + bounds.height);
}
}
else {
g.drawLine(x + 2, bounds.y, x + 2, bounds.y + bounds.height);
}
}
bounds.width = Math.max(bounds.width, x - bounds.x);
for (int x : gridInfo.vLines) {
g.drawLine(x, 0, x, gridInfo.height);
}
for (int y : gridInfo.hLines) {
g.drawLine(0, y, gridInfo.width, y);
}
g.drawRect(0, 0, gridInfo.width - 1, gridInfo.height - 1);
g.drawRect(1, 1, gridInfo.width - 3, gridInfo.height - 3);
List<RadComponent> children = myContainer.getChildren();
RadComponent last = children.isEmpty() ? null : children.get(children.size() - 1);
if (myInsertType == GridInsertType.in_cell) {
Rectangle cellRect = getInsertRect(myExist);
for (RadComponent child : children) {
Rectangle childBounds = child.getBounds(this);
int y = childBounds.y + childBounds.height + ((RadViewComponent)child).getMargins().height;
if (child != last || y < bounds.getMaxY()) {
g.drawLine(bounds.x, y, bounds.x + bounds.width, y);
if (myExist) {
AlphaFeedback.fillRect2(g, cellRect.x, cellRect.y, cellRect.width, cellRect.height, Color.pink);
}
else {
AlphaFeedback.fillRect1(g, cellRect.x, cellRect.y, cellRect.width, cellRect.height, Color.green);
}
}
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
}
@@ -0,0 +1,31 @@
/*
* 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.table;
import com.intellij.designer.model.RadComponent;
import com.intellij.util.ArrayUtil;
/**
* @author Alexander Lobas
*/
public class GridInfo {
public int width;
public int height;
public int[] hLines = ArrayUtil.EMPTY_INT_ARRAY;
public int[] vLines = ArrayUtil.EMPTY_INT_ARRAY;
public boolean[] emptyColumns = ArrayUtil.EMPTY_BOOLEAN_ARRAY;
public RadComponent[][] components;
}
@@ -0,0 +1,29 @@
/*
* 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.table;
/**
* @author Alexander Lobas
*/
public enum GridInsertType {
in_cell,
before_h_cell,
after_h_cell,
before_v_cell,
after_v_cell,
before_row,
after_row
}
@@ -30,12 +30,20 @@ public class RadCaptionTableColumn extends RadVisualComponent {
private final RadTableLayoutComponent myContainer;
private final int myColumnOffset;
private final int myColumnWidth;
private final StaticDecorator myDecorator = new CaptionStaticDecorator(this);
private final StaticDecorator myDecorator;
public RadCaptionTableColumn(RadTableLayoutComponent container, int columnOffset, int columnWidth) {
public RadCaptionTableColumn(RadTableLayoutComponent container, int columnOffset, int columnWidth, boolean empty) {
myContainer = container;
myColumnOffset = columnOffset;
myColumnWidth = columnWidth;
if (empty) {
myDecorator = new CaptionStaticDecorator(this, Color.PINK);
}
else {
myDecorator = new CaptionStaticDecorator(this);
}
setNativeComponent(container.getNativeComponent());
}
@@ -29,10 +29,17 @@ import java.util.List;
*/
public class RadCaptionTableRow extends RadVisualComponent {
private final RadViewComponent myComponent;
private final StaticDecorator myDecorator = new CaptionStaticDecorator(this);
private final StaticDecorator myDecorator;
public RadCaptionTableRow(RadViewComponent component) {
myComponent = component;
if (RadTableRowLayout.is(component)) {
myDecorator = new CaptionStaticDecorator(this);
} else {
myDecorator = new CaptionStaticDecorator(this, Color.PINK);
}
setNativeComponent(component.getNativeComponent());
}
@@ -97,7 +97,7 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco
@Override
public ComponentDecorator getChildSelectionDecorator(RadComponent component, List<RadComponent> selection) {
if (component.getLayout() instanceof RadTableRowLayout) {
if (RadTableRowLayout.is(component)) {
return super.getChildSelectionDecorator(component, selection);
}
@@ -165,10 +165,15 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco
List<RadComponent> components = new ArrayList<RadComponent>();
if (horizontal) {
int columnOffset = 0;
for (int columnWidth : container.getColumnWidths()) {
components.add(new RadCaptionTableColumn(container, columnOffset, Math.max(columnWidth, 2)));
columnOffset += columnWidth;
GridInfo gridInfo = container.getGridInfo();
int[] lines = gridInfo.vLines;
boolean[] emptyColumns = gridInfo.emptyColumns;
for (int i = 0; i < lines.length - 1; i++) {
components.add(new RadCaptionTableColumn(container,
lines[i],
lines[i + 1] - lines[i],
emptyColumns[i]));
}
}
else {
@@ -16,40 +16,175 @@
package com.intellij.android.designer.model.table;
import com.android.ide.common.rendering.api.ViewInfo;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.android.designer.model.RadViewContainer;
import com.intellij.designer.model.RadComponent;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class RadTableLayoutComponent extends RadViewContainer {
private int[] myColumnWidths;
private GridInfo myGridInfo;
private GridInfo myVirtualGridInfo;
@Override
public void setViewInfo(ViewInfo viewInfo) {
super.setViewInfo(viewInfo);
myColumnWidths = null;
myGridInfo = null;
myVirtualGridInfo = null;
}
@Nullable
public int[] getColumnWidths() {
if (myColumnWidths == null) {
try {
Object viewObject = myViewInfo.getViewObject();
Class<?> viewClass = viewObject.getClass();
Field maxWidths = viewClass.getDeclaredField("mMaxWidths");
maxWidths.setAccessible(true);
myColumnWidths = (int[])maxWidths.get(viewObject);
private int[] getColumnWidths() {
try {
Object viewObject = myViewInfo.getViewObject();
Class<?> viewClass = viewObject.getClass();
Field maxWidths = viewClass.getDeclaredField("mMaxWidths");
maxWidths.setAccessible(true);
int[] columnWidths = (int[])maxWidths.get(viewObject);
return columnWidths == null ? ArrayUtil.EMPTY_INT_ARRAY : columnWidths;
}
catch (Throwable e) {
return ArrayUtil.EMPTY_INT_ARRAY;
}
}
public GridInfo getGridInfo() {
if (myGridInfo == null) {
myGridInfo = new GridInfo();
int[] columnWidths = getColumnWidths();
if (columnWidths.length > 0) {
myGridInfo.emptyColumns = new boolean[columnWidths.length];
myGridInfo.vLines = new int[columnWidths.length + 1];
for (int i = 0; i < columnWidths.length; i++) {
int width = Math.max(columnWidths[i], 0);
myGridInfo.emptyColumns[i] = width == 0;
if (width == 0) {
width = 2;
if (i + 1 < columnWidths.length) {
columnWidths[i + 1] -= width;
}
}
myGridInfo.width += width;
myGridInfo.vLines[i + 1] = myGridInfo.width;
}
}
catch (Throwable e) {
}
if (myColumnWidths == null) {
myColumnWidths = ArrayUtil.EMPTY_INT_ARRAY;
List<RadComponent> rows = getChildren();
if (!rows.isEmpty()) {
Rectangle bounds = getBounds();
if (columnWidths.length == 0) {
myGridInfo.width = bounds.width;
}
myGridInfo.hLines = new int[rows.size() + 1];
int index = 1;
for (RadComponent row : rows) {
Rectangle rowBounds = row.getBounds();
myGridInfo.hLines[index++] = myGridInfo.height = rowBounds.y - bounds.y + rowBounds.height;
}
}
}
return myColumnWidths;
return myGridInfo;
}
public GridInfo getVirtualGridInfo() {
if (myVirtualGridInfo == null) {
myVirtualGridInfo = new GridInfo();
GridInfo gridInfo = getGridInfo();
Rectangle bounds = getBounds();
myVirtualGridInfo.width = bounds.width;
myVirtualGridInfo.height = bounds.height;
myVirtualGridInfo.vLines = addLineInfo(gridInfo.vLines, bounds.width - (gridInfo.vLines.length == 0 ? 0 : gridInfo.width));
myVirtualGridInfo.hLines = addLineInfo(gridInfo.hLines, bounds.height - gridInfo.height);
List<RadComponent> rows = getChildren();
if (!rows.isEmpty()) {
int columnSize = Math.max(1, gridInfo.vLines.length - 1);
RadComponent[][] components = new RadComponent[rows.size()][columnSize];
myVirtualGridInfo.components = components;
for (int i = 0; i < components.length; i++) {
RadComponent row = rows.get(i);
if (RadTableRowLayout.is(row)) {
int index = 0;
for (RadComponent column : row.getChildren()) {
int cellIndex = getCellIndex(column);
if (cellIndex > index) {
index = cellIndex;
}
int cellSpan = getCellSnap(column);
for (int j = 0; j < cellSpan; j++) {
components[i][index++] = column;
}
}
}
else {
components[i][0] = row;
}
}
}
}
return myVirtualGridInfo;
}
private static int getCellIndex(RadComponent component) {
try {
String column = ((RadViewComponent)component).getTag().getAttributeValue("android:layout_column");
return Integer.parseInt(column);
}
catch (Throwable e) {
return -1;
}
}
private static int getCellSnap(RadComponent component) {
try {
String span = ((RadViewComponent)component).getTag().getAttributeValue("android:layout_span");
return Integer.parseInt(span);
}
catch (Throwable e) {
return 1;
}
}
private static final int NEW_CELL_SIZE = 32;
private static int[] addLineInfo(int[] oldLines, int delta) {
if (delta > 0) {
int newLength = oldLines.length + delta / NEW_CELL_SIZE;
if (newLength > oldLines.length) {
int[] newLines = new int[newLength];
int startIndex = oldLines.length;
if (oldLines.length > 0) {
System.arraycopy(oldLines, 0, newLines, 0, oldLines.length);
}
else {
startIndex = 1;
}
for (int i = startIndex; i < newLength; i++) {
newLines[i] = newLines[i - 1] + NEW_CELL_SIZE;
}
return newLines;
}
return oldLines;
}
return oldLines;
}
}
@@ -36,6 +36,10 @@ public class RadTableRowLayout extends RadLinearLayout {
return LAYOUT_PARAMS;
}
public static boolean is(RadComponent component) {
return component.getLayout() instanceof RadTableRowLayout;
}
private boolean isTableParent() {
return myContainer.getParent() instanceof RadTableLayoutComponent;
}
@@ -57,4 +57,32 @@ public class AlphaFeedback extends JComponent {
protected void paintOther2(Graphics2D g2d) {
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// Utils
//
//////////////////////////////////////////////////////////////////////////////////////////
public static void fillRect1(Graphics g, int x, int y, int width, int height, Color color) {
fillRect(g, x, y, width, height, color, myComposite1);
}
public static void fillRect2(Graphics g, int x, int y, int width, int height, Color color) {
fillRect(g, x, y, width, height, color, myComposite2);
}
public static void fillRect(Graphics g, int x, int y, int width, int height, Color color, Composite composite) {
Graphics2D g2d = (Graphics2D)g;
Composite oldComposite = g2d.getComposite();
Color oldColor = g.getColor();
g2d.setColor(color);
g2d.setComposite(composite);
g2d.fillRect(x, y, width, height);
g2d.setComposite(oldComposite);
g2d.setColor(oldColor);
}
}