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