IDEA-40712
[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   private boolean mySpanInsertMode;
45
46   public GridInsertLocation(@NotNull final RadContainer container,
47                             final int row,
48                             final int column,
49                             final GridInsertMode mode) {
50     super(container, row, column);
51     myMode = mode;
52     assert container.getLayoutManager().isGrid();
53   }
54
55   public GridInsertLocation normalize() {
56     final RadAbstractGridLayoutManager gridManager = myContainer.getGridLayoutManager();
57
58     if (myMode == GridInsertMode.RowBefore && myRow > 0) {
59       myMode = GridInsertMode.RowAfter;
60       myRow--;
61     }
62     else if (myMode == GridInsertMode.ColumnBefore && myColumn > 0) {
63       myMode = GridInsertMode.ColumnAfter;
64       myColumn--;
65     }
66
67     if (myMode == GridInsertMode.RowAfter && gridManager.isGapCell(myContainer, true, myRow)) {
68       myRow--;
69     }
70     else if (myMode == GridInsertMode.RowBefore && gridManager.isGapCell(myContainer, true, myRow)) {
71       myRow++;
72     }
73     else if (myMode == GridInsertMode.ColumnAfter && gridManager.isGapCell(myContainer, false, myColumn)) {
74       myColumn--;
75     }
76     else if (myMode == GridInsertMode.ColumnBefore && gridManager.isGapCell(myContainer, false, myColumn)) {
77       myColumn++;
78     }
79
80     return this;
81   }
82
83   public GridInsertMode getMode() {
84     return myMode;
85   }
86
87   public void setSpanInsertMode(boolean spanInsertMode) {
88     mySpanInsertMode = spanInsertMode;
89   }
90
91   private boolean isColumnInsert() {
92     return myMode == GridInsertMode.ColumnAfter || myMode == GridInsertMode.ColumnBefore;
93   }
94
95   private boolean isRowInsert() {
96     return myMode == GridInsertMode.RowAfter || myMode == GridInsertMode.RowBefore;
97   }
98
99   public boolean isInsert() {
100     return isColumnInsert() || isRowInsert();
101   }
102
103   private int getInsertCell() {
104     return isRowInsert() ? myRow : myColumn;
105   }
106
107   private int getOppositeCell() {
108     return isRowInsert() ? myColumn : myRow;
109   }
110
111   private boolean isInsertAfter() {
112     return myMode == GridInsertMode.ColumnAfter || myMode == GridInsertMode.RowAfter;
113   }
114
115   @Override public boolean canDrop(ComponentDragObject dragObject) {
116     Rectangle rc = getDragObjectDimensions(dragObject, true);
117     int size = isRowInsert() ? rc.width : rc.height;
118     if (isInsertInsideComponent(size)) {
119       LOG.debug("GridInsertLocation.canDrop()=false because insert inside component");
120       return false;
121     }
122
123     // TODO[yole]: any other conditions to check here?
124     LOG.debug("GridInsertLocation.canDrop()=true");
125     return true;
126   }
127
128   private static boolean isSameCell(final ComponentDragObject dragObject, boolean isRow) {
129     if (dragObject.getComponentCount() == 0) {
130       return true;
131     }
132     int cell = isRow ? dragObject.getRelativeRow(0) : dragObject.getRelativeCol(0);
133     for(int i=1; i<dragObject.getComponentCount(); i++) {
134       int cell2 = isRow ? dragObject.getRelativeRow(i) : dragObject.getRelativeCol(i);
135       if (cell2 != cell) {
136         return false;
137       }
138     }
139     return true;
140   }
141
142   private boolean isInsertInsideComponent(final int size) {
143     int endColumn = getInsertCell();
144     if (isInsertAfter()) endColumn++;
145     int row = getOppositeCell();
146
147     for(int r=row; r<row+size; r++) {
148       for(int col = 0; col<endColumn; col++) {
149         RadComponent component;
150         if (isColumnInsert()) {
151           component = RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), r, col);
152         }
153         else {
154           component = RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), col, r);
155         }
156
157         if (component != null) {
158           GridConstraints constraints = component.getConstraints();
159           final boolean isRow = !isColumnInsert();
160           if (constraints.getCell(isRow) + constraints.getSpan(isRow) > endColumn &&
161               constraints.getSpan(isRow) > 1) {
162             return true;
163           }
164         }
165       }
166     }
167     return false;
168   }
169
170   @Override public void placeFeedback(FeedbackLayer feedbackLayer, ComponentDragObject dragObject) {
171     final int insertCol = getColumn();
172     final int insertRow = getRow();
173
174     Rectangle feedbackRect = getGridFeedbackRect(dragObject, isColumnInsert(), isRowInsert(), false);
175     if (feedbackRect == null) {
176       feedbackLayer.removeFeedback();
177       return;
178     }
179     Rectangle cellRect = getGridFeedbackCellRect(dragObject, isColumnInsert(), isRowInsert(), false);
180     assert cellRect != null;
181
182     final RadAbstractGridLayoutManager layoutManager = getContainer().getGridLayoutManager();
183     int[] vGridLines = layoutManager.getVerticalGridLines(getContainer());
184     int[] hGridLines = layoutManager.getHorizontalGridLines(getContainer());
185
186     FeedbackPainter painter = (myMode == GridInsertMode.ColumnBefore ||
187                                myMode == GridInsertMode.ColumnAfter)
188                               ? VertInsertFeedbackPainter.INSTANCE
189                               : HorzInsertFeedbackPainter.INSTANCE;
190     Rectangle rc;
191
192     Rectangle rcFeedback = null;
193     if (dragObject.getComponentCount() == 1) {
194       int lastColIndex = insertCol + dragObject.getColSpan(0);
195       if (lastColIndex > vGridLines.length - 1) {
196         lastColIndex = insertCol + 1;
197       }
198       
199       int lastRowIndex = insertRow + dragObject.getRowSpan(0);
200       if (lastRowIndex > hGridLines.length - 1) {
201         lastRowIndex = insertRow + 1;
202       }
203       
204       int cellWidth = vGridLines [lastColIndex] - vGridLines [insertCol];
205       int cellHeight = hGridLines [lastRowIndex] - hGridLines [insertRow];
206       RadComponent component = layoutManager.getComponentAtGrid(getContainer(), insertRow, insertCol);
207       if (component != null && mySpanInsertMode) {
208         Rectangle bounds = component.getBounds();
209         bounds.translate(-vGridLines [insertCol], -hGridLines [insertRow]);
210
211         int spaceToRight = vGridLines [lastColIndex] - vGridLines [insertCol] - (bounds.x + bounds.width);
212         int spaceBelow = hGridLines [lastRowIndex] - hGridLines [insertRow] - (bounds.y + bounds.height);
213         if (myMode == GridInsertMode.RowBefore && bounds.y > INSERT_RECT_MIN_SIZE) {
214           rcFeedback = new Rectangle(0, 0, cellWidth, bounds.y);
215         }
216         else if (myMode == GridInsertMode.RowAfter && spaceBelow > INSERT_RECT_MIN_SIZE) {
217           rcFeedback = new Rectangle(0, bounds.y + bounds.height, cellWidth, spaceBelow);
218         }
219         else if (myMode == GridInsertMode.ColumnBefore && bounds.x > INSERT_RECT_MIN_SIZE) {
220           rcFeedback = new Rectangle(0, 0, bounds.x, cellHeight);
221         }
222         else if (myMode == GridInsertMode.ColumnAfter && spaceToRight > INSERT_RECT_MIN_SIZE) {
223           rcFeedback = new Rectangle(bounds.x + bounds.width, 0, spaceToRight, cellHeight);
224         }
225
226         if (rcFeedback != null) {
227           boolean spanInsertMode = false;
228           
229           if (isRowInsert()) {
230             int columns = layoutManager.getGridColumnCount(getContainer());
231             for (int i = 0; i < columns; i++) {
232               if (i != insertCol && RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), insertRow, i) != null) {
233                 spanInsertMode = true;
234                 break;
235               }
236             }
237           } else {
238             int rows = layoutManager.getGridRowCount(getContainer());
239             for (int i = 0; i < rows; i++) {
240               if (i != insertRow && RadAbstractGridLayoutManager.getComponentAtGrid(getContainer(), i, insertCol) != null) {
241                 spanInsertMode = true;
242                 break;
243               }
244             }
245           }
246
247           if (!spanInsertMode) {
248             rcFeedback = null;
249           }
250         }
251
252         if (rcFeedback != null) {
253           rcFeedback.translate(vGridLines [insertCol], hGridLines [insertRow]);
254         }
255       }
256
257       if (rcFeedback == null) {
258         if (insertCol == layoutManager.getGridColumnCount(getContainer())-1 && myMode == GridInsertMode.ColumnAfter) {
259           final Dimension initialSize = dragObject.getInitialSize(getContainer());
260           int feedbackX = vGridLines [vGridLines.length-1] + layoutManager.getGapCellSize(myContainer, false);
261           int remainingSize = getContainer().getDelegee().getWidth() - feedbackX;
262           if (!dragObject.isHGrow() && remainingSize > initialSize.width) {
263            if (dragObject.isVGrow() || initialSize.height > cellHeight) {
264               rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow], initialSize.width, cellHeight);
265             }
266             else {
267               rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow] + (cellHeight - initialSize.height)/2,
268                                          initialSize.width, initialSize.height);
269             }
270           }
271           else if (remainingSize >= 4) {
272             rcFeedback = new Rectangle(feedbackX, hGridLines [insertRow], remainingSize, cellHeight);
273           }
274         }
275         else if (insertRow == layoutManager.getGridRowCount(getContainer())-1 && myMode == GridInsertMode.RowAfter) {
276           final Dimension initialSize = dragObject.getInitialSize(getContainer());
277           int feedbackY = hGridLines [hGridLines.length-1] + layoutManager.getGapCellSize(myContainer, true);
278           int remainingSize = getContainer().getDelegee().getHeight() - feedbackY;
279           if (!dragObject.isVGrow() && remainingSize > initialSize.height) {
280             rcFeedback = new Rectangle(vGridLines [insertCol], feedbackY, cellWidth, initialSize.height);
281           }
282           else if (remainingSize >= 4) {
283             rcFeedback = new Rectangle(vGridLines [insertCol], feedbackY, cellWidth, remainingSize);
284           }
285         }
286       }
287     }
288
289     if (rcFeedback != null) {
290       feedbackLayer.putFeedback(getContainer().getDelegee(), rcFeedback, getInsertFeedbackTooltip());
291       return;
292     }
293
294     rc = getInsertFeedbackPosition(myMode, getContainer(), cellRect, feedbackRect);
295     feedbackLayer.putFeedback(getContainer().getDelegee(), rc, painter, getInsertFeedbackTooltip());
296   }
297
298   private String getInsertFeedbackTooltip() {
299     int displayRow = myRow + getContainer().getGridLayoutManager().getCellIndexBase();
300     int displayColumn = myColumn + getContainer().getGridLayoutManager().getCellIndexBase();
301     String displayName = getContainer().getDisplayName();
302     switch(myMode) {
303       case ColumnBefore: return UIDesignerBundle.message("insert.feedback.before.col", displayName, displayRow, displayColumn);
304       case ColumnAfter:  return UIDesignerBundle.message("insert.feedback.after.col", displayName, displayRow, displayColumn);
305       case RowBefore:    return UIDesignerBundle.message("insert.feedback.before.row", displayName, displayColumn, displayRow);
306       case RowAfter:     return UIDesignerBundle.message("insert.feedback.after.row", displayName, displayColumn, displayRow);
307     }
308     return null;
309   }
310
311   public static Rectangle getInsertFeedbackPosition(final GridInsertMode mode, final RadContainer container, final Rectangle cellRect,
312                                                     final Rectangle feedbackRect) {
313     final RadAbstractGridLayoutManager manager = container.getGridLayoutManager();
314     int[] vGridLines = manager.getVerticalGridLines(container);
315     int[] hGridLines = manager.getHorizontalGridLines(container);
316
317     Rectangle rc = feedbackRect;
318     int w=4;
319     switch (mode) {
320       case ColumnBefore:
321         rc = new Rectangle(vGridLines [cellRect.x] - w, feedbackRect.y - INSERT_ARROW_SIZE,
322                            2 * w, feedbackRect.height + 2 * INSERT_ARROW_SIZE);
323         if (cellRect.x > 0 && manager.isGapCell(container, false, cellRect.x-1)) {
324           rc.translate(-(vGridLines [cellRect.x] - vGridLines [cellRect.x-1]) / 2, 0);
325         }
326         break;
327
328       case ColumnAfter:
329         rc = new Rectangle(vGridLines [cellRect.x + cellRect.width+1] - w, feedbackRect.y - INSERT_ARROW_SIZE,
330                            2 * w, feedbackRect.height + 2 * INSERT_ARROW_SIZE);
331         if (cellRect.x < manager.getGridColumnCount(container)-1 && manager.isGapCell(container, false, cellRect.x+1)) {
332           rc.translate((vGridLines [cellRect.x+2] - vGridLines [cellRect.x+1]) / 2, 0);
333         }
334         break;
335
336       case RowBefore:
337         rc = new Rectangle(feedbackRect.x - INSERT_ARROW_SIZE, hGridLines [cellRect.y] - w,
338                            feedbackRect.width + 2 * INSERT_ARROW_SIZE, 2 * w);
339         if (cellRect.y > 0 && manager.isGapCell(container, true, cellRect.y-1)) {
340           rc.translate(0, -(hGridLines [cellRect.y] - hGridLines [cellRect.y-1]) / 2);
341         }
342         break;
343
344       case RowAfter:
345         rc = new Rectangle(feedbackRect.x - INSERT_ARROW_SIZE, hGridLines [cellRect.y+cellRect.height+1] - w,
346                            feedbackRect.width + 2 * INSERT_ARROW_SIZE, 2 * w);
347         if (cellRect.y < manager.getGridRowCount(container)-1 && manager.isGapCell(container, true, cellRect.y+1)) {
348           rc.translate(0, (hGridLines [cellRect.y+2] - hGridLines [cellRect.y+1]) / 2);
349         }
350         break;
351     }
352
353     return rc;
354   }
355
356
357   @Override
358   public void processDrop(final GuiEditor editor,
359                           final RadComponent[] components,
360                           @Nullable final GridConstraints[] constraintsToAdjust,
361                           final ComponentDragObject dragObject) {
362     int row = getRow();
363     int col = getColumn();
364     RadContainer container = getContainer();
365     boolean canGrow = isRowInsert() ? dragObject.isVGrow() : dragObject.isHGrow();
366     int cell = isRowInsert() ? getRow() : getColumn();
367
368     int cellsToInsert = 1;
369     if (components.length > 0) {
370       int cellSize = container.getGridCellCount(isRowInsert());
371         Rectangle rc = getDragObjectDimensions(dragObject, cell < cellSize - 1);
372         int size = isRowInsert() ? rc.height : rc.width;
373         if (size > 0) {
374           cellsToInsert = size;
375       }
376     }
377
378     GridSpanInsertProcessor spanInsertProcessor =
379       mySpanInsertMode && dragObject.getComponentCount() == 1 ? new GridSpanInsertProcessor(container, getRow(), getColumn(), myMode,
380                                                                                             dragObject) : null;
381
382     int newCell = insertGridCells(container, cell, cellsToInsert, canGrow, isRowInsert(), !isInsertAfter(), constraintsToAdjust);
383     if (isRowInsert()) {
384       row = newCell;
385     }
386     else {
387       col = newCell;
388     }
389
390     if (components.length > 0) {
391       if (spanInsertProcessor != null) {
392         spanInsertProcessor.doBefore(newCell);
393       }
394
395       dropIntoGrid(container, components, row, col, dragObject);
396
397       if (spanInsertProcessor != null) {
398         spanInsertProcessor.doAfter(newCell);
399       }
400     }
401   }
402
403   private static int insertGridCells(RadContainer container, int cell, int cellsToInsert, boolean canGrow, boolean isRow, boolean isBefore,
404                                      GridConstraints[] constraintsToAdjust) {
405     int insertedCells = 1;
406     for(int i=0; i<cellsToInsert; i++) {
407       insertedCells = container.getGridLayoutManager().insertGridCells(container, cell, isRow, isBefore, canGrow);
408     }
409     // for insert after, shift only by one cell + possibly one gap cell, not by entire number of insertions
410     if (!isBefore) {
411       cell += insertedCells;
412     }
413     checkAdjustConstraints(constraintsToAdjust, isRow, cell, insertedCells);
414     return cell;
415   }
416
417   private static void checkAdjustConstraints(@Nullable final GridConstraints[] constraintsToAdjust,
418                                              final boolean isRow,
419                                              final int index, final int count) {
420     if (constraintsToAdjust != null) {
421       for(GridConstraints constraints: constraintsToAdjust) {
422         GridChangeUtil.adjustConstraintsOnInsert(constraints, isRow, index, count);
423       }
424     }
425   }
426
427
428   @NonNls @Override public String toString() {
429     return "GridInsertLocation(" + myMode.toString() + ", row=" + getRow() + ", col=" + getColumn() + ")";
430   }
431
432   @Override @Nullable
433   public ComponentDropLocation getAdjacentLocation(Direction direction) {
434     if (isRowInsert()) {
435       if (direction == Direction.RIGHT) {
436         if (getColumn() < myContainer.getGridColumnCount()-1) {
437           return new GridInsertLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, getColumn()+1, false, 1), getMode());
438         }
439         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.ColumnAfter);
440       }
441       if (direction == Direction.LEFT) {
442         if (getColumn() > 0) {
443           return new GridInsertLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, getColumn()-1, false, -1), getMode());
444         }
445         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.ColumnBefore);
446       }
447       if (direction == Direction.DOWN || direction == Direction.UP) {
448         int adjRow = (myMode == GridInsertMode.RowAfter) ? getRow() : getRow()-1;
449         if (direction == Direction.DOWN && adjRow+1 < myContainer.getGridRowCount()) {
450           return new GridDropLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, adjRow+1, true, 1), getColumn());
451         }
452         if (direction == Direction.UP && adjRow >= 0) {
453           return new GridDropLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, adjRow, true, -1), getColumn());
454         }
455         return getLocationAtParent(direction);
456       }
457     }
458     else {
459       if (direction == Direction.DOWN) {
460         if (getRow() < myContainer.getGridRowCount()-1) {
461           return new GridInsertLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, getRow()+1, true, 1), getColumn(), getMode());
462         }
463         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.RowAfter);
464       }
465       if (direction == Direction.UP) {
466         if (getRow() > 0) {
467           return new GridInsertLocation(myContainer, FormEditingUtil.adjustForGap(myContainer, getRow()-1, true, -1), getColumn(), getMode());
468         }
469         return new GridInsertLocation(myContainer, getRow(), getColumn(), GridInsertMode.RowBefore);
470       }
471       if (direction == Direction.LEFT || direction == Direction.RIGHT) {
472         int adjCol = (myMode == GridInsertMode.ColumnAfter) ? getColumn() : getColumn()-1;
473         if (direction == Direction.RIGHT && adjCol+1 < myContainer.getGridColumnCount()) {
474           return new GridDropLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, adjCol+1, false, 1));
475         }
476         if (direction == Direction.LEFT && adjCol >= 0) {
477           return new GridDropLocation(myContainer, getRow(), FormEditingUtil.adjustForGap(myContainer, adjCol, false, -1));
478         }
479         return getLocationAtParent(direction);
480       }
481     }
482     return null;
483   }
484
485   private ComponentDropLocation getLocationAtParent(final Direction direction) {
486     final RadContainer parent = myContainer.getParent();
487     if (parent.getLayoutManager().isGrid()) {
488       final GridConstraints c = myContainer.getConstraints();
489       switch(direction) {
490         case LEFT: return new GridInsertLocation(parent, c.getRow(), c.getColumn(), GridInsertMode.ColumnBefore);
491         case RIGHT: return new GridInsertLocation(parent, c.getRow(), c.getColumn()+c.getColSpan()-1, GridInsertMode.ColumnAfter);
492         case UP: return new GridInsertLocation(parent, c.getRow(), c.getColumn(), GridInsertMode.RowBefore);
493         case DOWN: return new GridInsertLocation(parent, c.getRow()+c.getRowSpan()-1, c.getColumn(), GridInsertMode.RowAfter);
494       }
495     }
496     return null;
497   }
498 }