/* * 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.actions; import com.android.SdkConstants; import com.intellij.android.designer.designSurface.feedbacks.TextFeedback; import com.intellij.android.designer.designSurface.graphics.DirectionResizePoint; import com.intellij.android.designer.designSurface.graphics.DrawingStyle; import com.intellij.android.designer.designSurface.graphics.RectangleFeedback; import com.intellij.android.designer.designSurface.layout.grid.GridSelectionDecorator; import com.intellij.android.designer.model.RadComponentOperations; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.grid.GridInfo; import com.intellij.android.designer.model.grid.IGridProvider; import com.intellij.designer.designSurface.DecorationLayer; import com.intellij.designer.designSurface.EditOperation; 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.LineMarginBorder; import com.intellij.designer.model.RadComponent; import com.intellij.designer.utils.Position; import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.xml.XmlTag; import com.intellij.ui.JBColor; import com.intellij.util.containers.IntArrayList; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.List; /** * @author Alexander Lobas */ public abstract class LayoutSpanOperation implements EditOperation { public static final String TYPE = "layout_span"; protected static final Color COLOR = JBColor.GREEN.darker(); protected final OperationContext myContext; private final GridSelectionDecorator myDecorator; protected RadViewComponent myComponent; private RectangleFeedback myFeedback; private TextFeedback myTextFeedback; private ErrorFeedback myErrorFeedback; private Rectangle myBounds; private Rectangle myContainerBounds; private boolean myShowErrorFeedback; protected int mySpan; private int[] mySpans; private int[] myOffsets; private int[] myCells; private int myIndex = -1; public LayoutSpanOperation(OperationContext context, GridSelectionDecorator decorator) { myContext = context; myDecorator = decorator; } @Override public void setComponent(RadComponent component) { myComponent = (RadViewComponent)component; myBounds = myDecorator.getCellBounds(myContext.getArea().getFeedbackLayer(), myComponent); } @Override public void setComponents(List<RadComponent> components) { } protected void createFeedback() { if (myFeedback == null) { FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); myTextFeedback = new TextFeedback(); myTextFeedback.setBorder(new LineMarginBorder(0, 5, 3, 0)); layer.add(myTextFeedback); myFeedback = new RectangleFeedback(DrawingStyle.RESIZE_PREVIEW); layer.add(myFeedback); myErrorFeedback = new ErrorFeedback(); layer.add(myErrorFeedback); layer.repaint(); } } @Override public void showFeedback() { createFeedback(); int direction = myContext.getResizeDirection(); if (direction == Position.WEST || direction == Position.EAST) { handleColumns(direction == Position.EAST); } else { handleRows(direction == Position.SOUTH); } } @Override public void eraseFeedback() { if (myFeedback != null) { FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); layer.remove(myTextFeedback); layer.remove(myFeedback); layer.remove(myErrorFeedback); layer.repaint(); myTextFeedback = null; myFeedback = null; myErrorFeedback = null; } } protected abstract String getColumnAttribute(boolean asName); protected abstract String getColumnSpanAttribute(boolean asName); protected abstract String getRowAttribute(boolean asName); protected abstract String getRowSpanAttribute(boolean asName); private void handleRows(boolean bottom) { calculateRows(bottom); Rectangle bounds = myContext.getTransformedRectangle(myBounds); int location = bottom ? bounds.y + bounds.height : bounds.y; if (location < myOffsets[0]) { myIndex = 0; myErrorFeedback.setVisible(!bottom && myShowErrorFeedback); } else { myIndex = -1; for (int i = 0; i < myOffsets.length - 1; i++) { if (myOffsets[i] <= location && location <= myOffsets[i + 1]) { int delta1 = location - myOffsets[i]; int delta2 = myOffsets[i + 1] - location; myIndex = delta2 >= delta1 ? i : i + 1; break; } } if (myIndex == -1) { myIndex = myOffsets.length - 1; myErrorFeedback.setVisible(bottom && myShowErrorFeedback); } else { myErrorFeedback.setVisible(false); } } if (bottom) { myFeedback.setBounds(myBounds.x, myBounds.y, myBounds.width, myOffsets[myIndex] - myBounds.y); } else { myFeedback.setBounds(myBounds.x, myOffsets[myIndex], myBounds.width, myBounds.y + myBounds.height - myOffsets[myIndex]); } myTextFeedback.clear(); if (!bottom) { myTextFeedback.append(getRowAttribute(true)); myTextFeedback.append(" "); myTextFeedback.append(Integer.toString(myCells[myIndex])); myTextFeedback.append(", "); } myTextFeedback.append(getRowSpanAttribute(true)); myTextFeedback.append(" "); myTextFeedback.append(Integer.toString(mySpans[myIndex])); myTextFeedback.centerTop(myContainerBounds); } private void handleColumns(boolean right) { calculateColumns(right); Rectangle bounds = myContext.getTransformedRectangle(myBounds); int location = right ? bounds.x + bounds.width : bounds.x; if (location < myOffsets[0]) { myIndex = 0; myErrorFeedback.setVisible(!right && myShowErrorFeedback); } else { myIndex = -1; for (int i = 0; i < myOffsets.length - 1; i++) { if (myOffsets[i] <= location && location <= myOffsets[i + 1]) { int delta1 = location - myOffsets[i]; int delta2 = myOffsets[i + 1] - location; myIndex = delta2 >= delta1 ? i : i + 1; break; } } if (myIndex == -1) { myIndex = myOffsets.length - 1; myErrorFeedback.setVisible(right && myShowErrorFeedback); } else { myErrorFeedback.setVisible(false); } } if (right) { myFeedback.setBounds(myBounds.x, myBounds.y, myOffsets[myIndex] - myBounds.x, myBounds.height); } else { myFeedback.setBounds(myOffsets[myIndex], myBounds.y, myBounds.x + myBounds.width - myOffsets[myIndex], myBounds.height); } myTextFeedback.clear(); if (!right) { myTextFeedback.append(getColumnAttribute(true)); myTextFeedback.append(" "); myTextFeedback.append(Integer.toString(myCells[myIndex])); myTextFeedback.append(", "); } myTextFeedback.append(getColumnSpanAttribute(true)); myTextFeedback.append(" "); myTextFeedback.append(Integer.toString(mySpans[myIndex])); myTextFeedback.centerTop(myContainerBounds); } protected RadComponent getContainer() { return myComponent.getParent(); } protected abstract Point getCellInfo(); private void calculateRows(boolean bottom) { if (mySpans != null) { return; } RadComponent container = getContainer(); GridInfo gridInfo = ((IGridProvider)container).getVirtualGridInfo(); RadComponent[][] components = gridInfo.components; Point cellInfo = getCellInfo(); int row = cellInfo.y; FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); myContainerBounds = container.getBounds(layer); IntArrayList spans = new IntArrayList(); IntArrayList offsets = new IntArrayList(); if (bottom) { int span = 1; for (int i = row; i < row + mySpan; i++) { spans.add(span++); offsets.add(myContainerBounds.y + gridInfo.getCellPosition(layer, i + 1, 0).y); } for (int i = row + mySpan; i < components.length; i++) { if (components[i][cellInfo.x] == null) { spans.add(span++); offsets.add(myContainerBounds.y + gridInfo.getCellPosition(layer, i + 1, 0).y); } else { myErrorFeedback.setBounds(myDecorator.getCellBounds(layer, components[i][cellInfo.x])); myShowErrorFeedback = true; break; } } } else { IntArrayList columns = new IntArrayList(); int span = mySpan; for (int i = row; i < row + mySpan; i++) { spans.add(span--); offsets.add(myContainerBounds.y + gridInfo.getCellPosition(layer, i, 0).y); columns.add(i); } span = mySpan; for (int i = row - 1; i >= 0; i--) { if (components[i][cellInfo.x] == null) { spans.add(0, ++span); offsets.add(0, myContainerBounds.y + gridInfo.getCellPosition(layer, i, 0).y); columns.add(0, i); } else { myErrorFeedback.setBounds(myDecorator.getCellBounds(layer, components[i][cellInfo.x])); myShowErrorFeedback = true; break; } } myCells = columns.toArray(); } mySpans = spans.toArray(); myOffsets = offsets.toArray(); } private void calculateColumns(boolean right) { if (mySpans != null) { return; } RadComponent container = getContainer(); GridInfo gridInfo = ((IGridProvider)container).getVirtualGridInfo(); Point cellInfo = getCellInfo(); RadComponent[] rowComponents = gridInfo.components[cellInfo.y]; int column = cellInfo.x; FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); myContainerBounds = container.getBounds(layer); IntArrayList spans = new IntArrayList(); IntArrayList offsets = new IntArrayList(); if (right) { int span = 1; for (int i = column; i < column + mySpan; i++) { spans.add(span++); offsets.add(myContainerBounds.x + gridInfo.getCellPosition(layer, 0, i + 1).x); } for (int i = column + mySpan; i < rowComponents.length; i++) { if (rowComponents[i] == null) { spans.add(span++); offsets.add(myContainerBounds.x + gridInfo.getCellPosition(layer, 0, i + 1).x); } else { myErrorFeedback.setBounds(myDecorator.getCellBounds(layer, rowComponents[i])); myShowErrorFeedback = true; break; } } } else { IntArrayList columns = new IntArrayList(); int span = mySpan; for (int i = column; i < column + mySpan; i++) { spans.add(span--); offsets.add(myContainerBounds.x + gridInfo.getCellPosition(layer, 0, i).x); columns.add(i); } span = mySpan; for (int i = column - 1; i >= 0; i--) { if (rowComponents[i] == null) { spans.add(0, ++span); offsets.add(0, myContainerBounds.x + gridInfo.getCellPosition(layer, 0, i).x); columns.add(0, i); } else { myErrorFeedback.setBounds(myDecorator.getCellBounds(layer, rowComponents[i])); myShowErrorFeedback = true; break; } } myCells = columns.toArray(); } mySpans = spans.toArray(); myOffsets = offsets.toArray(); } @Override public boolean canExecute() { return true; } @Override public void execute() throws Exception { if (mySpans[myIndex] == mySpan) { return; } ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { int direction = myContext.getResizeDirection(); if (direction == Position.WEST || direction == Position.EAST) { execute(getColumnAttribute(false), getColumnSpanAttribute(false), direction == Position.WEST); } else { execute(getRowAttribute(false), getRowSpanAttribute(false), direction == Position.NORTH); } } }); } private void execute(String cell, String span, boolean cellFix) { XmlTag tag = myComponent.getTag(); if (cellFix) { tag.setAttribute(cell, SdkConstants.NS_RESOURCES, Integer.toString(myCells[myIndex])); } int spanValue = mySpans[myIndex]; if (spanValue == 1) { RadComponentOperations.deleteAttribute(tag, span); } else { tag.setAttribute(span, SdkConstants.NS_RESOURCES, Integer.toString(spanValue)); } } private static class ErrorFeedback extends AlphaFeedback { public ErrorFeedback() { super(JBColor.PINK); } @Override protected void paintOther1(Graphics2D g2d) { } @Override protected void paintOther2(Graphics2D g2d) { g2d.fillRect(0, 0, getWidth(), getHeight()); } } ////////////////////////////////////////////////////////////////////////////////////////// // // ResizePoint // ////////////////////////////////////////////////////////////////////////////////////////// protected static class SpanPoint extends DirectionResizePoint { private final GridSelectionDecorator myDecorator; public SpanPoint(int direction, Object type, @Nullable String description, GridSelectionDecorator decorator) { super(DrawingStyle.RESIZE_SPAN, direction, type, description); myDecorator = decorator; } @Override protected Rectangle getBounds(DecorationLayer layer, RadComponent component) { return myDecorator.getCellBounds(layer, component); } } }