/* * 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.designSurface.layout.grid; import com.intellij.android.designer.designSurface.AbstractEditOperation; import com.intellij.android.designer.designSurface.feedbacks.TextFeedback; import com.intellij.android.designer.designSurface.graphics.DesignerGraphics; import com.intellij.android.designer.designSurface.graphics.DrawingStyle; import com.intellij.android.designer.designSurface.graphics.InsertFeedback; import com.intellij.android.designer.model.grid.GridInfo; import com.intellij.android.designer.model.grid.GridInsertType; import com.intellij.android.designer.model.grid.IGridProvider; import com.intellij.designer.designSurface.EditableArea; import com.intellij.designer.designSurface.FeedbackLayer; import com.intellij.designer.designSurface.OperationContext; import com.intellij.designer.model.RadComponent; import com.intellij.ui.IdeBorderFactory; import javax.swing.*; import java.awt.*; /** * @author Alexander Lobas */ public abstract class GridOperation extends AbstractEditOperation { private GridFeedback myFeedback; private InsertFeedback myInsertFeedback; private TextFeedback myTextFeedback; private Rectangle myBounds; protected int myColumn; protected int myRow; protected GridInsertType myInsertType; protected boolean myExist; public GridOperation(RadComponent container, OperationContext context) { super(container, context); } protected final GridInfo getGridInfo() { return ((IGridProvider)myContainer).getVirtualGridInfo(); } private void createFeedback() { if (myFeedback == null) { FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); myInsertFeedback = new InsertFeedback(DrawingStyle.DROP_ZONE_ACTIVE); layer.add(myInsertFeedback); myBounds = myContainer.getBounds(layer); myFeedback = new GridFeedback(); myFeedback.setBounds(myBounds); layer.add(myFeedback); myTextFeedback = new TextFeedback(); myTextFeedback.setBorder(IdeBorderFactory.createEmptyBorder(0, 3, 2, 0)); layer.add(myTextFeedback); layer.repaint(); } } @Override public void showFeedback() { createFeedback(); calculateGridInfo(); configureTextFeedback(); myFeedback.repaint(); } private void configureTextFeedback() { myTextFeedback.clear(); int row = myRow; int column = myColumn; myTextFeedback.append("["); if (myInsertType == GridInsertType.before_h_cell) { myTextFeedback.append("before "); } else if (myInsertType == GridInsertType.after_h_cell) { myTextFeedback.append("after "); } else if (myInsertType != GridInsertType.in_cell) { myTextFeedback.append("insert: "); if (myInsertType == GridInsertType.corner_top_right) { column++; } else if (myInsertType == GridInsertType.corner_bottom_left) { row++; } else if (myInsertType == GridInsertType.corner_bottom_right) { row++; column++; } } myTextFeedback.append("row "); myTextFeedback.bold(Integer.toString(row)); myTextFeedback.append(", "); if (myInsertType == GridInsertType.before_v_cell) { myTextFeedback.append("before "); } else if (myInsertType == GridInsertType.after_v_cell) { myTextFeedback.append("after "); } myTextFeedback.append("column "); myTextFeedback.bold(Integer.toString(column)); myTextFeedback.append("]"); myTextFeedback.centerTop(myBounds); } @Override public void eraseFeedback() { if (myFeedback != null) { FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); layer.remove(myInsertFeedback); layer.remove(myFeedback); layer.remove(myTextFeedback); layer.repaint(); myFeedback = null; myInsertFeedback = null; myTextFeedback = null; } } @Override public boolean canExecute() { return myComponents.size() == 1 && (myInsertType != GridInsertType.in_cell || !myExist); } @Override public abstract void execute() throws Exception; ////////////////////////////////////////////////////////////////////////////////////////// // // Grid // ////////////////////////////////////////////////////////////////////////////////////////// private static final int CROSS_SIZE = 10; private void calculateGridInfo() { GridInfo gridInfo = getGridInfo(); Point location = gridInfo.grid.toModel(myContext.getArea().getNativeComponent(), myContext.getLocation()); location.x -= gridInfo.grid.getBounds().x; location.y -= gridInfo.grid.getBounds().y; myColumn = getLineIndex(gridInfo.vLines, location.x); myRow = getLineIndex(gridInfo.hLines, location.y); if (gridInfo.components == null) { myInsertType = GridInsertType.in_cell; myExist = false; } else { myExist = isExist(myRow, myColumn); myInsertType = GridInsertType.in_cell; Rectangle cellRect = getInsertRect(myExist); Rectangle inCellRect = getInsertInRect(cellRect); boolean isExistCell = gridInfo.components != null && myRow < gridInfo.components.length && myColumn < gridInfo.components[0].length; if (!inCellRect.contains(location)) { if (location.x <= inCellRect.x) { if (location.y <= inCellRect.y) { if (isExistCell) { myInsertType = GridInsertType.corner_top_left; myInsertFeedback.cross(myBounds.x + cellRect.x, myBounds.y + cellRect.y, CROSS_SIZE); } } else if (inCellRect.y < location.y && location.y < inCellRect.getMaxY()) { if (myExist && (myColumn == 0 || isExist(myRow, myColumn - 1))) { boolean insert = true; if (isMoveOperation()) { if (myColumn != 0 || getMovedIndex(false) == 0) { insert = !isSingleMovedAxis(false); } } if (insert) { myInsertType = GridInsertType.before_v_cell; cellRect = getInsertRect(false); myInsertFeedback.vertical(myBounds.x + cellRect.x, myBounds.y + cellRect.y, cellRect.height); } } } else if (isExistCell) { myInsertType = GridInsertType.corner_bottom_left; myInsertFeedback.cross(myBounds.x + cellRect.x, myBounds.y + cellRect.y + cellRect.height, CROSS_SIZE); } } else if (location.x >= inCellRect.getMaxX()) { if (location.y <= inCellRect.y) { if (isExistCell) { myInsertType = GridInsertType.corner_top_right; myInsertFeedback.cross(myBounds.x + cellRect.x + cellRect.width, myBounds.y + cellRect.y, CROSS_SIZE); } } else if (inCellRect.y < location.y && location.y < inCellRect.getMaxY()) { if (myExist && (myColumn == gridInfo.lastInsertColumn || isExist(myRow, myColumn + 1))) { if (!isMoveOperation() || !isSingleMovedAxis(false)) { myInsertType = GridInsertType.after_v_cell; cellRect = getInsertRect(false); myInsertFeedback.vertical(myBounds.x + cellRect.x + cellRect.width, myBounds.y + cellRect.y, cellRect.height); } } } else if (isExistCell) { myInsertType = GridInsertType.corner_bottom_right; myInsertFeedback.cross(myBounds.x + cellRect.x + cellRect.width, myBounds.y + cellRect.y + cellRect.height, CROSS_SIZE); } } else if (location.y <= inCellRect.y) { if (myExist && (myRow == 0 || isExist(myRow - 1, myColumn))) { boolean insert = true; if (isMoveOperation()) { if (myRow != 0 || getMovedIndex(true) == 0) { insert = !isSingleMovedAxis(true); } } if (insert) { myInsertType = GridInsertType.before_h_cell; cellRect = getInsertRect(false); myInsertFeedback.horizontal(myBounds.x + cellRect.x, myBounds.y + cellRect.y, cellRect.width); } } } else if (location.y >= inCellRect.getMaxY()) { if (myExist && (myRow == gridInfo.lastInsertRow || isExist(myRow + 1, myColumn))) { if (!isMoveOperation() || !isSingleMovedAxis(true)) { myInsertType = GridInsertType.after_h_cell; cellRect = getInsertRect(false); myInsertFeedback.horizontal(myBounds.x + cellRect.x, myBounds.y + cellRect.y + cellRect.height, cellRect.width); } } } } } if (myInsertType == GridInsertType.in_cell) { myInsertFeedback.setVisible(false); } } protected boolean isMoveOperation() { return myContext.isMove(); } 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 boolean isExist(int row, int column) { RadComponent[][] components = getGridInfo().components; if (components != null && 0 <= row && row < components.length && 0 <= column && column < components[0].length) { return components[row][column] != null; } return false; } private Rectangle getInsertRect(boolean includeSpans) { GridInfo gridInfo = getGridInfo(); int startColumn = myColumn; int endColumn = myColumn + 1; int startRow = myRow; int endRow = myRow + 1; if (includeSpans) { 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; } } while (startRow > 0) { if (gridInfo.components[startRow - 1][startColumn] == existComponent) { startRow--; } else { break; } } while (endRow < gridInfo.components.length) { if (gridInfo.components[endRow][startColumn] == existComponent) { endRow++; } else { break; } } } EditableArea area = myContext.getArea(); JComponent target = area.getNativeComponent(); int x1 = startColumn < gridInfo.vLines.length ? gridInfo.getCellPosition(target, 0, startColumn).x : 0; int x2 = endColumn < gridInfo.vLines.length ? gridInfo.getCellPosition(target, 0, endColumn).x : gridInfo.getSize(target).width; int y1 = startRow < gridInfo.hLines.length ? gridInfo.getCellPosition(target, startRow, 0).y : 0; int y2 = endRow < gridInfo.hLines.length ? gridInfo.getCellPosition(target, endRow, 0).y : gridInfo.getSize(target).height; return new Rectangle(x1, y1, x2 - x1, y2 - y1); } private static Rectangle getInsertInRect(Rectangle cellRect) { int borderWidth = Math.min(cellRect.width / 3, 10); int borderHeight = Math.min(cellRect.height / 3, 10); return new Rectangle(cellRect.x + borderWidth, cellRect.y + borderHeight, cellRect.width - 2 * borderWidth, cellRect.height - 2 * borderHeight); } protected abstract int getMovedIndex(boolean row); protected abstract boolean isSingleMovedAxis(boolean row); protected final int getSizeInRow(int rowIndex, RadComponent excludeComponent) { int size = 0; RadComponent[][] components = getGridInfo().components; if (rowIndex < components.length) { RadComponent[] rowComponents = components[rowIndex]; for (int j = 0; j < rowComponents.length; j++) { RadComponent cellComponent = rowComponents[j]; if (cellComponent != null) { if (cellComponent != excludeComponent) { size++; } while (j + 1 < rowComponents.length && cellComponent == rowComponents[j + 1]) { j++; } } } } return size; } protected final int getSizeInColumn(int columnIndex, int columnCount, RadComponent excludeComponent) { int size = 0; RadComponent[][] components = getGridInfo().components; if (columnIndex < columnCount) { for (int j = 0; j < components.length; j++) { RadComponent cellComponent = components[j][columnIndex]; if (cellComponent != null) { if (cellComponent != excludeComponent) { size++; } while (j + 1 < components.length && cellComponent == components[j + 1][columnIndex]) { j++; } } } } return size; } ////////////////////////////////////////////////////////////////////////////////////////// // // Feedback // ////////////////////////////////////////////////////////////////////////////////////////// private class GridFeedback extends JComponent { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); DesignerGraphics.useStroke(DrawingStyle.DROP_ZONE, g); GridInfo gridInfo = getGridInfo(); Dimension size = gridInfo.getSize(this); if (gridInfo.vLines.length > 0 && gridInfo.hLines.length > 0) { for (int column = 0; column < gridInfo.vLines.length; column++) { int x = gridInfo.getCellPosition(this, 0, column).x; g.drawLine(x, 0, x, size.height); } for (int row = 0; row < gridInfo.hLines.length; row++) { int y = gridInfo.getCellPosition(this, row, 0).y; g.drawLine(0, y, size.width, y); } } g.drawRect(0, 0, size.width - 1, size.height - 1); g.drawRect(1, 1, size.width - 3, size.height - 3); DesignerGraphics.drawRect(DrawingStyle.DROP_RECIPIENT, g, 0, 0, size.width, size.height); if (myInsertType == GridInsertType.in_cell) { Rectangle cellRect = getInsertRect(myExist); if (myExist) { DesignerGraphics.drawFilledRect(DrawingStyle.INVALID, g, cellRect.x, cellRect.y, cellRect.width + 1, cellRect.height + 1); } else { DesignerGraphics.drawFilledRect(DrawingStyle.DROP_ZONE_ACTIVE, g, cellRect.x, cellRect.y, cellRect.width + 1, cellRect.height + 1); } } } } }