41f0a32cfc65f2c432eabedb001a266eadb5227c
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / designSurface / GridInsertLocation.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.uiDesigner.designSurface;
18
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.uiDesigner.FormEditingUtil;
21 import com.intellij.uiDesigner.GridChangeUtil;
22 import com.intellij.uiDesigner.UIDesignerBundle;
23 import com.intellij.uiDesigner.core.GridConstraints;
24 import com.intellij.uiDesigner.radComponents.RadAbstractGridLayoutManager;
25 import com.intellij.uiDesigner.radComponents.RadComponent;
26 import com.intellij.uiDesigner.radComponents.RadContainer;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.awt.*;
32
33 /**
34  * @author yole
35  */
36 public class GridInsertLocation extends GridDropLocation {
37   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.designSurface.GridInsertLocation");
38
39   public static final int INSERT_ARROW_SIZE = 3;
40   public static final int INSERT_RECT_MIN_SIZE = 15;  // should be larger than the insets increase on Shift
41
42   private GridInsertMode myMode;
43
44   public GridInsertLocation(@NotNull final RadContainer container,
45                             final int row,
46                             final int column,
47                             final GridInsertMode mode) {
48     super(container, row, column);
49     myMode = mode;
50     assert container.getLayoutManager().isGrid();
51   }
52
53   public GridInsertLocation normalize() {
54     final RadAbstractGridLayoutManager gridManager = myContainer.getGridLayoutManager();
55
56     if (myMode == GridInsertMode.RowBefore && myRow > 0) {
57       myMode = GridInsertMode.RowAfter;
58       myRow--;
59     }
60     else if (myMode == GridInsertMode.ColumnBefore && myColumn > 0) {
61       myMode = GridInsertMode.ColumnAfter;
62       myColumn--;
63     }
64
65     if (myMode == GridInsertMode.RowAfter && gridManager.isGapCell(myContainer, true, myRow)) {
66       myRow--;
67     }
68     else if (myMode == GridInsertMode.RowBefore && gridManager.isGapCell(myContainer, true, myRow)) {
69       myRow++;
70     }
71     else if (myMode == GridInsertMode.ColumnAfter && gridManager.isGapCell(myContainer, false, myColumn)) {
72       myColumn--;
73     }
74     else if (myMode == GridInsertMode.ColumnBefore && gridManager.isGapCell(myContainer, false, myColumn)) {
75       myColumn++;
76     }
77
78     return this;
79   }
80
81   public GridInsertMode getMode() {
82     return myMode;
83   }
84
85   private boolean isColumnInsert() {
86     return myMode == GridInsertMode.ColumnAfter || myMode == GridInsertMode.ColumnBefore;
87   }
88
89   private boolean isRowInsert() {
90     return myMode == GridInsertMode.RowAfter || myMode == GridInsertMode.RowBefore;
91   }
92
93   public boolean isInsert() {
94     return isColumnInsert() || isRowInsert();
95   }
96
97   private int getInsertCell() {
98     return isRowInsert() ? myRow : myColumn;
99   }
100
101   private int getOppositeCell() {
102     return isRowInsert() ? myColumn : myRow;
103   }
104
105   private boolean isInsertAfter() {
106     return myMode == GridInsertMode.ColumnAfter || myMode == GridInsertMode.RowAfter;
107   }
108
109   @Override public boolean canDrop(ComponentDragObject dragObject) {
110     Rectangle rc = getDragObjectDimensions(dragObject, true);
111     int size = isRowInsert() ? rc.width : rc.height;
112     if (isInsertInsideComponent(size)) {
113       LOG.debug("GridInsertLocation.canDrop()=false because insert inside component");
114       return false;
115     }
116
117     // TODO[yole]: any other conditions to check here?
118     LOG.debug("GridInsertLocation.canDrop()=true");
119     return true;
120   }
121
122   private static boolean isSameCell(final ComponentDragObject dragObject, boolean isRow) {
123     if (dragObject.getComponentCount() == 0) {
124       return true;
125     }
126     int cell = isRow ? dragObject.getRelativeRow(0) : dragObject.getRelativeCol(0);
127     for(int i=1; i<dragObject.getComponentCount(); i++) {
128       int cell2 = isRow ? dragObject.getRelativeRow(i) : dragObject.getRelativeCol(i);
129       if (cell2 != cell) {
130         return false;
131       }
132     }
133     return true;
134   }
135
136   private boolean isInsertInsideComponent(final int size) {
137     int endColumn = getInsertCell();
138     if (isInsertAfter()) endColumn++;
139     int row = getOppositeCell();
140
141     for(int r=row; r<row+size; r++) {
142       for(int col = 0; col<endColumn; col++) {
143         RadComponent component;
144         if (isColumnInsert()) {
145           component = RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), r, col);
146         }
147         else {
148           component = RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), col, r);
149         }
150
151         if (component != null) {
152           GridConstraints constraints = component.getConstraints();
153           final boolean isRow = !isColumnInsert();
154           if (constraints.getCell(isRow) + constraints.getSpan(isRow) > endColumn &&
155               constraints.getSpan(isRow) > 1) {
156             return true;
157           }
158         }
159       }
160     }
161     return false;
162   }
163
164   @Override public void placeFeedback(FeedbackLayer feedbackLayer, ComponentDragObject dragObject) {
165     final int insertCol = getColumn();
166     final int insertRow = getRow();
167
168     Rectangle feedbackRect = getGridFeedbackRect(dragObject, isColumnInsert(), isRowInsert(), false);
169     if (feedbackRect == null) {
170       feedbackLayer.removeFeedback();
171       return;
172     }
173     Rectangle cellRect = getGridFeedbackCellRect(dragObject, isColumnInsert(), isRowInsert(), false);
174     assert cellRect != null;
175
176     final RadAbstractGridLayoutManager layoutManager = getContainer().getGridLayoutManager();
177     int[] vGridLines = layoutManager.getVerticalGridLines(getContainer());
178     int[] hGridLines = layoutManager.getHorizontalGridLines(getContainer());
179
180     FeedbackPainter painter = (myMode == GridInsertMode.ColumnBefore ||
181                                myMode == GridInsertMode.ColumnAfter)
182                               ? VertInsertFeedbackPainter.INSTANCE
183                               : HorzInsertFeedbackPainter.INSTANCE;
184     Rectangle rc;
185
186     Rectangle rcFeedback = null;
187     if (dragObject.getComponentCount() == 1) {
188       int lastColIndex = insertCol + dragObject.getColSpan(0);
189       if (lastColIndex > vGridLines.length - 1) {
190         lastColIndex = insertCol + 1;
191       }
192       
193       int lastRowIndex = insertRow + dragObject.getRowSpan(0);
194       if (lastRowIndex > hGridLines.length - 1) {
195         lastRowIndex = insertRow + 1;
196       }
197       
198       int cellWidth = vGridLines [lastColIndex] - vGridLines [insertCol];
199       int cellHeight = hGridLines [lastRowIndex] - hGridLines [insertRow];
200       RadComponent component = layoutManager.getComponentAtGrid(getContainer(), insertRow, insertCol);
201       if (component != null) {
202         Rectangle bounds = component.getBounds();
203         bounds.translate(-vGridLines [insertCol], -hGridLines [insertRow]);
204
205         int spaceToRight = vGridLines [lastColIndex] - vGridLines [insertCol] - (bounds.x + bounds.width);
206         int spaceBelow = hGridLines [lastRowIndex] - hGridLines [insertRow] - (bounds.y + bounds.height);
207         if (myMode == GridInsertMode.RowBefore && bounds.y > INSERT_RECT_MIN_SIZE) {
208           rcFeedback = new Rectangle(0, 0, cellWidth, bounds.y);
209         }
210         else if (myMode == GridInsertMode.RowAfter && spaceBelow > INSERT_RECT_MIN_SIZE) {
211           rcFeedback = new Rectangle(0, bounds.y + bounds.height, cellWidth, spaceBelow);
212         }
213         else if (myMode == GridInsertMode.ColumnBefore && bounds.x > INSERT_RECT_MIN_SIZE) {
214           rcFeedback = new Rectangle(0, 0, bounds.x, cellHeight);
215         }
216         else if (myMode == GridInsertMode.ColumnAfter && spaceToRight > INSERT_RECT_MIN_SIZE) {
217           rcFeedback = new Rectangle(bounds.x + bounds.width, 0, spaceToRight, cellHeight);
218         }
219
220         if (rcFeedback != null) {
221           rcFeedback.translate(vGridLines [insertCol], hGridLines [insertRow]);
222         }
223       }
224
225       if (rcFeedback == null) {
226         if (insertCol == layoutManager.getGridColumnCount(getContainer())-1 && myMode == GridInsertMode.ColumnAfter) {
227           final Dimension initialSize = dragObject.getInitialSize(getContainer());
228           int feedbackX = vGridLines [vGridLines.length-1] + layoutManager.getGapCellSize(myContainer, false);
229           int remainingSize = getContainer().getDelegee().getWidth() - feedbackX;
230           if (!dragObject.isHGrow() && remainingSize > initialSize.width) {
231            if (dragObject.isVGrow() || initialSize.height > cellHeight) {
232               rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow], initialSize.width, cellHeight);
233             }
234             else {
235               rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow] + (cellHeight - initialSize.height)/2,
236                                          initialSize.width, initialSize.height);
237             }
238           }
239           else if (remainingSize >= 4) {
240             rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow], remainingSize, cellHeight);
241           }
242         }
243         else if (insertRow == layoutManager.getGridRowCount(getContainer())-1 && myMode == GridInsertMode.RowAfter) {
244           final Dimension initialSize = dragObject.getInitialSize(getContainer());
245           int feedbackY = hGridLines [hGridLines.length-1] + layoutManager.getGapCellSize(myContainer, true);
246           int remainingSize = getContainer().getDelegee().getHeight() - feedbackY;
247           if (!dragObject.isVGrow() && remainingSize > initialSize.height) {
248             rcFeedback = new Rectangle(vGridLines [insertCol], feedbackY, cellWidth, initialSize.height);
249           }
250           else if (remainingSize >= 4) {
251             rcFeedback = new Rectangle(vGridLines [insertCol], feedbackY, cellWidth, remainingSize);
252           }
253         }
254       }
255     }
256
257     if (rcFeedback != null) {
258       feedbackLayer.putFeedback(getContainer().getDelegee(), rcFeedback, getInsertFeedbackTooltip());
259       return;
260     }
261
262     rc = getInsertFeedbackPosition(myMode, getContainer(), cellRect, feedbackRect);
263     feedbackLayer.putFeedback(getContainer().getDelegee(), rc, painter, getInsertFeedbackTooltip());
264   }
265
266   private String getInsertFeedbackTooltip() {
267     int displayRow = myRow + getContainer().getGridLayoutManager().getCellIndexBase();
268     int displayColumn = myColumn + getContainer().getGridLayoutManager().getCellIndexBase();
269     String displayName = getContainer().getDisplayName();
270     switch(myMode) {
271       case ColumnBefore: return UIDesignerBundle.message("insert.feedback.before.col", displayName, displayRow, displayColumn);
272       case ColumnAfter:  return UIDesignerBundle.message("insert.feedback.after.col", displayName, displayRow, displayColumn);
273       case RowBefore:    return UIDesignerBundle.message("insert.feedback.before.row", displayName, displayColumn, displayRow);
274       case RowAfter:     return UIDesignerBundle.message("insert.feedback.after.row", displayName, displayColumn, displayRow);
275     }
276     return null;
277   }
278
279   public static Rectangle getInsertFeedbackPosition(final GridInsertMode mode, final RadContainer container, final Rectangle cellRect,
280                                                     final Rectangle feedbackRect) {
281     final RadAbstractGridLayoutManager manager = container.getGridLayoutManager();
282     int[] vGridLines = manager.getVerticalGridLines(container);
283     int[] hGridLines = manager.getHorizontalGridLines(container);
284
285     Rectangle rc = feedbackRect;
286     int w=4;
287     switch (mode) {
288       case ColumnBefore:
289         rc = new Rectangle(vGridLines [cellRect.x] - w, feedbackRect.y - INSERT_ARROW_SIZE,
290                            2 * w, feedbackRect.height + 2 * INSERT_ARROW_SIZE);
291         if (cellRect.x > 0 && manager.isGapCell(container, false, cellRect.x-1)) {
292           rc.translate(-(vGridLines [cellRect.x] - vGridLines [cellRect.x-1]) / 2, 0);
293         }
294         break;
295
296       case ColumnAfter:
297         rc = new Rectangle(vGridLines [cellRect.x + cellRect.width+1] - w, feedbackRect.y - INSERT_ARROW_SIZE,
298                            2 * w, feedbackRect.height + 2 * INSERT_ARROW_SIZE);
299         if (cellRect.x < manager.getGridColumnCount(container)-1 && manager.isGapCell(container, false, cellRect.x+1)) {
300           rc.translate((vGridLines [cellRect.x+2] - vGridLines [cellRect.x+1]) / 2, 0);
301         }
302         break;
303
304       case RowBefore:
305         rc = new Rectangle(feedbackRect.x - INSERT_ARROW_SIZE, hGridLines [cellRect.y] - w,
306                            feedbackRect.width + 2 * INSERT_ARROW_SIZE, 2 * w);
307         if (cellRect.y > 0 && manager.isGapCell(container, true, cellRect.y-1)) {
308           rc.translate(0, -(hGridLines [cellRect.y] - hGridLines [cellRect.y-1]) / 2);
309         }
310         break;
311
312       case RowAfter:
313         rc = new Rectangle(feedbackRect.x - INSERT_ARROW_SIZE, hGridLines [cellRect.y+cellRect.height+1] - w,
314                            feedbackRect.width + 2 * INSERT_ARROW_SIZE, 2 * w);
315         if (cellRect.y < manager.getGridRowCount(container)-1 && manager.isGapCell(container, true, cellRect.y+1)) {
316           rc.translate(0, (hGridLines [cellRect.y+2] - hGridLines [cellRect.y+1]) / 2);
317         }
318         break;
319     }
320
321     return rc;
322   }
323
324
325   @Override
326   public void processDrop(final GuiEditor editor,
327                           final RadComponent[] components,
328                           @Nullable final GridConstraints[] constraintsToAdjust,
329                           final ComponentDragObject dragObject) {
330     int row = getRow();
331     int col = getColumn();
332     RadContainer container = getContainer();
333     boolean canGrow = isRowInsert() ? dragObject.isVGrow() : dragObject.isHGrow();
334     int cell = isRowInsert() ? getRow() : getColumn();
335
336     int cellsToInsert = 1;
337     if (components.length > 0) {
338       int cellSize = container.getGridCellCount(isRowInsert());
339       Rectangle rc = getDragObjectDimensions(dragObject, cell < cellSize - 1);
340       int size = isRowInsert() ? rc.height : rc.width;
341       if (size > 0) {
342         cellsToInsert = size;
343       }
344     }
345
346     int newCell = insertGridCells(container, cell, cellsToInsert, canGrow, isRowInsert(), !isInsertAfter(), constraintsToAdjust);
347     if (isRowInsert()) {
348       row = newCell;
349     }
350     else {
351       col = newCell;
352     }
353
354     if (components.length > 0) {
355       dropIntoGrid(container, components, row, col, dragObject);
356     }
357   }
358
359   private static int insertGridCells(RadContainer container, int cell, int cellsToInsert, boolean canGrow, boolean isRow, boolean isBefore,
360                                      GridConstraints[] constraintsToAdjust) {
361     int insertedCells = 1;
362     for(int i=0; i<cellsToInsert; i++) {
363       insertedCells = container.getGridLayoutManager().insertGridCells(container, cell, isRow, isBefore, canGrow);
364     }
365     // for insert after, shift only by one cell + possibly one gap cell, not by entire number of insertions
366     if (!isBefore) {
367       cell += insertedCells;
368     }
369     checkAdjustConstraints(constraintsToAdjust, isRow, cell, insertedCells);
370     return cell;
371   }
372
373   private static void checkAdjustConstraints(@Nullable final GridConstraints[] constraintsToAdjust,
374                                              final boolean isRow,
375                                              final int index, final int count) {
376     if (constraintsToAdjust != null) {
377       for(GridConstraints constraints: constraintsToAdjust) {
378         GridChangeUtil.adjustConstraintsOnInsert(constraints, isRow, index, count);
379       }
380     }
381   }
382
383
384   @NonNls @Override public String toString() {
385     return "GridInsertLocation(" + myMode.toString() + ", row=" + getRow() + ", col=" + getColumn() + ")";
386   }
387
388   @Override @Nullable
389   public ComponentDropLocation getAdjacentLocation(Direction direction) {
390     if (isRowInsert()) {
391       if (direction == Direction.RIGHT) {
392         if (getColumn() < myContainer.getGridColumnCount()-1) {
393           return new GridInsertLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, getColumn()+1, false, 1), getMode());
394         }
395         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.ColumnAfter);
396       }
397       if (direction == Direction.LEFT) {
398         if (getColumn() > 0) {
399           return new GridInsertLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, getColumn()-1, false, -1), getMode());
400         }
401         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.ColumnBefore);
402       }
403       if (direction == Direction.DOWN || direction == Direction.UP) {
404         int adjRow = (myMode == GridInsertMode.RowAfter) ? getRow() : getRow()-1;
405         if (direction == Direction.DOWN && adjRow+1 < myContainer.getGridRowCount()) {
406           return new GridDropLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, adjRow+1, true, 1), getColumn());
407         }
408         if (direction == Direction.UP && adjRow >= 0) {
409           return new GridDropLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, adjRow, true, -1), getColumn());
410         }
411         return getLocationAtParent(direction);
412       }
413     }
414     else {
415       if (direction == Direction.DOWN) {
416         if (getRow() < myContainer.getGridRowCount()-1) {
417           return new GridInsertLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, getRow()+1, true, 1), getColumn(), getMode());
418         }
419         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.RowAfter);
420       }
421       if (direction == Direction.UP) {
422         if (getRow() > 0) {
423           return new GridInsertLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, getRow()-1, true, -1), getColumn(), getMode());
424         }
425         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.RowBefore);
426       }
427       if (direction == Direction.LEFT || direction == Direction.RIGHT) {
428         int adjCol = (myMode == GridInsertMode.ColumnAfter) ? getColumn() : getColumn()-1;
429         if (direction == Direction.RIGHT && adjCol+1 < myContainer.getGridColumnCount()) {
430           return new GridDropLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, adjCol+1, false, 1));
431         }
432         if (direction == Direction.LEFT && adjCol >= 0) {
433           return new GridDropLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, adjCol, false, -1));
434         }
435         return getLocationAtParent(direction);
436       }
437     }
438     return null;
439   }
440
441   private ComponentDropLocation getLocationAtParent(final Direction direction) {
442     final RadContainer parent = myContainer.getParent();
443     if (parent.getLayoutManager().isGrid()) {
444       final GridConstraints c = myContainer.getConstraints();
445       switch(direction) {
446         case LEFT: return new GridInsertLocation(parent, c.getRow(), c.getColumn(), GridInsertMode.ColumnBefore);
447         case RIGHT: return new GridInsertLocation(parent, c.getRow(), c.getColumn()+c.getColSpan()-1, GridInsertMode.ColumnAfter);
448         case UP: return new GridInsertLocation(parent, c.getRow(), c.getColumn(), GridInsertMode.RowBefore);
449         case DOWN: return new GridInsertLocation(parent, c.getRow()+c.getRowSpan()-1, c.getColumn(), GridInsertMode.RowAfter);
450       }
451     }
452     return null;
453   }
454 }