IDEA-40712
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / radComponents / RadAbstractGridLayoutManager.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.radComponents;
18
19 import com.intellij.openapi.actionSystem.ActionGroup;
20 import com.intellij.uiDesigner.GridChangeUtil;
21 import com.intellij.uiDesigner.UIFormXmlConstants;
22 import com.intellij.uiDesigner.XmlWriter;
23 import com.intellij.uiDesigner.FormEditingUtil;
24 import com.intellij.uiDesigner.core.GridConstraints;
25 import com.intellij.uiDesigner.designSurface.*;
26 import com.intellij.util.IncorrectOperationException;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import java.awt.*;
31 import java.beans.PropertyChangeEvent;
32 import java.beans.PropertyChangeListener;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38 /**
39  * @author yole
40  */
41 public abstract class RadAbstractGridLayoutManager extends RadLayoutManager {
42   protected final Map<RadComponent, MyPropertyChangeListener> myListenerMap = new HashMap<RadComponent, MyPropertyChangeListener>();
43
44   @Override
45   public boolean isGrid() {
46     return true;
47   }
48
49   public abstract int getGridRowCount(RadContainer container);
50   public abstract int getGridColumnCount(RadContainer container);
51
52   public int getGridCellCount(RadContainer container, boolean isRow) {
53     return isRow ? getGridRowCount(container) : getGridColumnCount(container);
54   }
55
56   public int getGridRowAt(RadContainer container, int y) {
57     return getGridCellAt(container, y, true);
58   }
59
60   public int getGridColumnAt(RadContainer container, int x) {
61     return getGridCellAt(container, x, false);
62   }
63
64   private int getGridCellAt(final RadContainer container, final int coord, final boolean isRow) {
65     int[] coords = getGridCellCoords(container, isRow);
66     int[] sizes = getGridCellSizes(container, isRow);
67     for (int i = 0; i < coords.length; i++) {
68       if (coords[i] <= coord && coord <= coords[i] + sizes[i]) {
69         return i;
70       }
71     }
72     return -1;
73   }
74
75   public abstract int[] getHorizontalGridLines(RadContainer container);
76   public abstract int[] getVerticalGridLines(RadContainer container);
77   public abstract int[] getGridCellCoords(RadContainer container, boolean isRow);
78   public abstract int[] getGridCellSizes(RadContainer container, boolean isRow);
79
80   @Nullable
81   public CustomPropertiesPanel getRowColumnPropertiesPanel(RadContainer container, boolean isRow, int[] selectedIndices) {
82     return null;
83   }
84
85   @Nullable
86   public static RadComponent getComponentAtGrid(RadContainer container, final int row, final int column) {
87     // If the target cell is not empty does not allow drop.
88     for(int i=0; i<container.getComponentCount(); i++){
89       final RadComponent component = container.getComponent(i);
90       if (component.isDragging()) {
91         continue;
92       }
93       final GridConstraints constraints=component.getConstraints();
94       if(
95         constraints.getRow() <= row && row < constraints.getRow()+constraints.getRowSpan() &&
96         constraints.getColumn() <= column && column < constraints.getColumn()+constraints.getColSpan()
97       ){
98         return component;
99       }
100     }
101     return null;
102   }
103
104   public int getGridLineNear(RadContainer container, boolean isRow, Point pnt, int epsilon) {
105     int coord = isRow ? pnt.y : pnt.x;
106     int[] gridLines = isRow ? getHorizontalGridLines(container) : getVerticalGridLines(container);
107     for(int col = 1; col <gridLines.length; col++) {
108       if (coord < gridLines [col]) {
109         if (coord - gridLines [col-1] < epsilon) {
110           return col-1;
111         }
112         if (gridLines [col] - coord < epsilon) {
113           return col;
114         }
115         return -1;
116       }
117     }
118     if (coord - gridLines [gridLines.length-1] < epsilon) {
119       return gridLines.length-1;
120     }
121     return -1;
122   }
123
124   public Rectangle getGridCellRangeRect(RadContainer container, int startRow, int startCol, int endRow, int endCol) {
125     int[] xs = getGridCellCoords(container, false);
126     int[] ys = getGridCellCoords(container, true);
127     int[] widths = getGridCellSizes(container, false);
128     int[] heights = getGridCellSizes(container, true);
129     return new Rectangle(xs[startCol],
130                          ys[startRow],
131                          xs[endCol] + widths[endCol] - xs[startCol],
132                          ys[endRow] + heights[endRow] - ys[startRow]);
133   }
134
135   public boolean canCellGrow(RadContainer container, boolean isRow, int i) {
136     return false;
137   }
138
139   @Nullable
140   public ActionGroup getCaptionActions() {
141     return null;
142   }
143
144   public void paintCaptionDecoration(final RadContainer container, final boolean isRow, final int i, final Graphics2D g,
145                                      final Rectangle rc) {
146     if (canCellGrow(container, isRow, i)) {
147       drawGrowMarker(isRow, g, rc);
148     }
149   }
150
151   /**
152    * @return the number of inserted rows or columns
153    */
154   public int insertGridCells(final RadContainer grid, final int cellIndex, final boolean isRow, final boolean isBefore, final boolean grow) {
155     GridChangeUtil.insertRowOrColumn(grid, cellIndex, isRow, isBefore);
156     return 1;
157   }
158
159   public void copyGridCells(RadContainer source, final RadContainer destination, final boolean isRow, int cellIndex, int cellCount, int targetIndex) {
160     for(int i=0; i< cellCount; i++) {
161       insertGridCells(destination, cellIndex, isRow, false, false);
162     }
163   }
164
165   /**
166    * @return the number of deleted rows or columns
167    */
168   public int deleteGridCells(final RadContainer grid, final int cellIndex, final boolean isRow) {
169     GridChangeUtil.deleteCell(grid, cellIndex, isRow);
170     return 1;
171   }
172
173   public void processCellsMoved(final RadContainer container, final boolean isRow, final int[] cells, final int targetCell) {
174     GridChangeUtil.moveCells(container, isRow, cells, targetCell);
175   }
176
177   public int getGapCellCount() {
178     return 0;
179   }
180
181   public int getGapCellSize(final RadContainer container, boolean isRow) {
182     return 0;
183   }
184
185   public boolean isGapCell(RadContainer grid, boolean isRow, int cellIndex) {
186     return false;
187   }
188
189   public int getCellIndexBase() {
190     return 0;
191   }
192
193   public boolean canSpanningAllowed() {
194     return true;
195   }
196
197   public boolean canResizeCells() {
198     return true;
199   }
200
201   @Nullable
202   public String getCellResizeTooltip(RadContainer container, boolean isRow, int cell, int newSize) {
203     return null;
204   }
205   public void processCellResized(RadContainer container, final boolean isRow, final int cell, final int newSize) {
206   }
207
208   public abstract void copyGridSection(final RadContainer source, final RadContainer destination, final Rectangle rc);
209
210   public LayoutManager copyLayout(LayoutManager layout, int rowDelta, int columnDelta) {
211     return layout;
212   }
213
214   /**
215    * Returns true if the dimensions of the grid in the container are defined by the coordinates of the components
216    * in the grid (and therefore it must not be validated that an inserted component fits the grid bounds).
217    */
218   public boolean isGridDefinedByComponents() {
219     return false;
220   }
221
222   protected static void writeGridConstraints(final XmlWriter writer, final RadComponent child) {
223     // Constraints in Grid layout
224     writer.startElement("grid");
225     try {
226       final GridConstraints constraints = child.getConstraints();
227       writer.addAttribute("row",constraints.getRow());
228       writer.addAttribute("column",constraints.getColumn());
229       writer.addAttribute("row-span",constraints.getRowSpan());
230       writer.addAttribute("col-span",constraints.getColSpan());
231       writer.addAttribute("vsize-policy",constraints.getVSizePolicy());
232       writer.addAttribute("hsize-policy",constraints.getHSizePolicy());
233       writer.addAttribute("anchor",constraints.getAnchor());
234       writer.addAttribute("fill",constraints.getFill());
235       writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_INDENT, constraints.getIndent());
236       writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_USE_PARENT_LAYOUT, constraints.isUseParentLayout());
237
238       // preferred size
239       writer.writeDimension(constraints.myMinimumSize,"minimum-size");
240       writer.writeDimension(constraints.myPreferredSize,"preferred-size");
241       writer.writeDimension(constraints.myMaximumSize,"maximum-size");
242     } finally {
243       writer.endElement(); // grid
244     }
245   }
246
247
248   @Override @NotNull
249   public ComponentDropLocation getDropLocation(RadContainer container, @Nullable final Point location) {
250     if (container.getGridRowCount() == 1 && container.getGridColumnCount() == 1 &&
251         getComponentAtGrid(container, 0, 0) == null) {
252       final Rectangle rc = getGridCellRangeRect(container, 0, 0, 0, 0);
253       if (location == null) {
254         return new FirstComponentInsertLocation(container, rc, 0, 0);
255       }
256       return new FirstComponentInsertLocation(container, location, rc);
257     }
258
259     if (location == null) {
260       if (getComponentAtGrid(container, 0, 0) == null) {
261         return new GridDropLocation(container, 0, 0);
262       }
263       return new GridInsertLocation(container, getLastNonSpacerRow(container), 0, GridInsertMode.RowAfter);
264     }
265
266     int[] xs = getGridCellCoords(container, false);
267     int[] ys = getGridCellCoords(container, true);
268     int[] widths = getGridCellSizes(container, false);
269     int[] heights = getGridCellSizes(container, true);
270
271     int[] horzGridLines = getHorizontalGridLines(container);
272     int[] vertGridLines = getVerticalGridLines(container);
273
274     int row=ys.length-1;
275     int col=xs.length-1;
276     for(int i=0; i<xs.length; i++) {
277       if (location.x < xs[i] + widths[i]) {
278         col=i;
279         break;
280       }
281     }
282     for(int i=0; i<ys.length; i++) {
283       if (location.getY() < ys [i]+heights [i]) {
284         row=i;
285         break;
286       }
287     }
288
289     GridInsertMode mode = null;
290
291     int EPSILON = 4;
292     int dy = (int)(location.getY() - ys [row]);
293     if (dy < EPSILON) {
294       mode = GridInsertMode.RowBefore;
295     }
296     else if (heights [row] - dy < EPSILON) {
297       mode = GridInsertMode.RowAfter;
298     }
299
300     int dx = location.x - xs[col];
301     if (dx < EPSILON) {
302       mode = GridInsertMode.ColumnBefore;
303     }
304     else if (widths [col] - dx < EPSILON) {
305       mode = GridInsertMode.ColumnAfter;
306     }
307
308     boolean spanInsertMode = canSpanningAllowed() && mode == null;
309     boolean normalize = true;
310     final int cellWidth = vertGridLines[col + 1] - vertGridLines[col];
311     final int cellHeight = horzGridLines[row + 1] - horzGridLines[row];
312     if (mode == null) {
313       RadComponent component = getComponentAtGrid(container, row, col);
314       if (component != null) {
315         Rectangle rc = component.getBounds();
316         rc.translate(-xs [col], -ys [row]);
317
318         int right = rc.x + rc.width + GridInsertLocation.INSERT_RECT_MIN_SIZE;
319         int bottom = rc.y + rc.height + GridInsertLocation.INSERT_RECT_MIN_SIZE;
320
321         if (dy < rc.y - GridInsertLocation.INSERT_RECT_MIN_SIZE) {
322           mode = GridInsertMode.RowBefore;
323         }
324         else if (dy > bottom && dy < cellHeight) {
325           mode = GridInsertMode.RowAfter;
326         }
327         if (dx < rc.x - GridInsertLocation.INSERT_RECT_MIN_SIZE) {
328           mode = GridInsertMode.ColumnBefore;
329         }
330         else if (dx > right && dx < cellWidth) {
331           mode = GridInsertMode.ColumnAfter;
332         }
333
334         normalize = false;
335       }
336     }
337
338     if (mode != null) {
339       GridInsertLocation dropLocation = new GridInsertLocation(container, row, col, mode);
340       dropLocation.setSpanInsertMode(spanInsertMode);
341       return normalize ? dropLocation.normalize() : dropLocation;
342     }
343     if (getComponentAtGrid(container, row, col) instanceof RadVSpacer ||
344         getComponentAtGrid(container, row, col) instanceof RadHSpacer) {
345       return new GridReplaceDropLocation(container, row, col);
346     }
347     return new GridDropLocation(container, row, col);
348   }
349
350   private int getLastNonSpacerRow(final RadContainer container) {
351     int lastRow = getGridRowCount(container)-1;
352     for(int col=0; col<getGridColumnCount(container); col++) {
353       RadComponent c = getComponentAtGrid(container, lastRow, col);
354       if (c != null && !(c instanceof RadHSpacer) && !(c instanceof RadVSpacer)) {
355         return lastRow;
356       }
357     }
358     return lastRow-1;
359   }
360
361   protected static void drawGrowMarker(final boolean isRow, final Graphics2D g2d, final Rectangle rc) {
362     g2d.setColor(Color.BLACK);
363     if (!isRow) {
364       int maxX = (int) rc.getMaxX();
365       int midY = (int) rc.getCenterY()+3;
366       final int xStart = Math.max(maxX - 10, rc.x + 2);
367       final int xEnd = maxX - 2;
368       g2d.drawLine(xStart, midY, xEnd, midY);
369       g2d.drawLine(xStart, midY, xStart+2, midY-2);
370       g2d.drawLine(xStart, midY, xStart+2, midY+2);
371       g2d.drawLine(xEnd, midY, xEnd-2, midY-2);
372       g2d.drawLine(xEnd, midY, xEnd-2, midY+2);
373     }
374     else {
375       int maxY = (int) rc.getMaxY();
376       int midX = (int) rc.getCenterX()+3;
377       final int yStart = Math.max(maxY - 10, rc.y + 2);
378       final int yEnd = maxY - 2;
379       g2d.drawLine(midX, yStart, midX, yEnd);
380       g2d.drawLine(midX, yStart, midX-2, yStart+2);
381       g2d.drawLine(midX, yStart, midX+2, yStart+2);
382       g2d.drawLine(midX, yEnd, midX-2, yEnd-2);
383       g2d.drawLine(midX, yEnd, midX+2, yEnd-2);
384     }
385   }
386
387   @Override
388   public void changeContainerLayout(RadContainer container) throws IncorrectOperationException {
389     if (container.getLayoutManager().isGrid()) {
390       RadAbstractGridLayoutManager grid = container.getGridLayoutManager();
391       List<Boolean> canRowsGrow = collectCanCellsGrow(grid, container, true);
392       List<Boolean> canColumnsGrow = collectCanCellsGrow(grid, container, false);
393       List<RadComponent> contents = collectComponents(container);
394
395       changeLayoutFromGrid(container, contents, canRowsGrow, canColumnsGrow);
396
397       int oldGapMultiplier = grid.getGapCellCount()+1;
398       int gapMultiplier = getGapCellCount()+1;
399       for(RadComponent c: contents) {
400         GridConstraints gc = c.getConstraints();
401         gc.setRow(gc.getRow() * gapMultiplier / oldGapMultiplier);
402         gc.setColumn(gc.getColumn() * gapMultiplier / oldGapMultiplier);
403         container.addComponent(c);
404       }
405     }
406     else if (container.getLayoutManager().isIndexed()) {
407       List<RadComponent> components = collectComponents(container);
408       changeLayoutFromIndexed(container, components);
409
410       int gapMultiplier = getGapCellCount()+1;
411       for(int i=0; i<components.size(); i++) {
412         GridConstraints gc = components.get(i).getConstraints();
413         gc.setRow(0);
414         gc.setColumn(i*gapMultiplier);
415         gc.setRowSpan(1);
416         gc.setColSpan(1);
417         container.addComponent(components.get(i));
418       }
419     }
420     else if (container.getComponentCount() == 0) {
421       container.setLayoutManager(this);
422     }
423     else {
424       throw new IncorrectOperationException("Cannot change from " + container.getLayout() + " to grid layout");
425     }
426   }
427
428   protected void changeLayoutFromGrid(final RadContainer container, final List<RadComponent> contents, final List<Boolean> canRowsGrow,
429                                     final List<Boolean> canColumnsGrow) {
430     container.setLayoutManager(this);
431   }
432
433   protected void changeLayoutFromIndexed(final RadContainer container, final List<RadComponent> components) {
434     container.setLayoutManager(this);
435   }
436
437   private static List<Boolean> collectCanCellsGrow(final RadAbstractGridLayoutManager grid, final RadContainer container, final boolean isRow) {
438     List<Boolean> result = new ArrayList<Boolean>();
439     for(int i=0; i<grid.getGridCellCount(container, isRow); i++) {
440       if (!grid.isGapCell(container, isRow, i)) {
441         result.add(grid.canCellGrow(container, isRow, i));
442       }
443     }
444     return result;
445   }
446
447   private static List<RadComponent> collectComponents(final RadContainer container) {
448     List<RadComponent> contents = new ArrayList<RadComponent>();
449     for(int i=container.getComponentCount()-1; i >= 0; i--) {
450       final RadComponent component = container.getComponent(i);
451       if (!(component instanceof RadHSpacer) && !(component instanceof RadVSpacer)) {
452         contents.add(0, component);
453       }
454       container.removeComponent(component);
455     }
456     return contents;
457   }
458
459   public boolean canMoveComponent(RadComponent c, int rowDelta, int colDelta, final int rowSpanDelta, final int colSpanDelta) {
460     final int newRow = getNewRow(c, rowDelta);
461     final int newCol = getNewColumn(c, colDelta);
462     final int newRowSpan = getNewRowSpan(c, rowSpanDelta);
463     final int newColSpan = getNewColSpan(c, colSpanDelta);
464     if (newRow < 0 || newCol < 0 || newRowSpan < 1 || newColSpan < 1 ||
465         newRow + newRowSpan > c.getParent().getGridRowCount() ||
466         newCol + newColSpan > c.getParent().getGridColumnCount()) {
467       return false;
468     }
469     c.setDragging(true);
470     final RadComponent overlap = c.getParent().findComponentInRect(newRow, newCol, newRowSpan, newColSpan);
471     c.setDragging(false);
472     if (overlap != null) {
473       return false;
474     }
475     return true;
476   }
477
478   private static int getNewRow(final RadComponent c, final int rowDelta) {
479     return FormEditingUtil.adjustForGap(c.getParent(), c.getConstraints().getRow() + rowDelta, true, rowDelta);
480   }
481
482   private static int getNewColumn(final RadComponent c, final int colDelta) {
483     return FormEditingUtil.adjustForGap(c.getParent(), c.getConstraints().getColumn() + colDelta, false, colDelta);
484   }
485
486   private static int getNewRowSpan(final RadComponent c, final int rowSpanDelta) {
487     int gapCount = c.getParent().getGridLayoutManager().getGapCellCount();
488     return c.getConstraints().getRowSpan() + rowSpanDelta * (gapCount+1);
489   }
490
491   private static int getNewColSpan(final RadComponent c, final int colSpanDelta) {
492     int gapCount = c.getParent().getGridLayoutManager().getGapCellCount();
493     return c.getConstraints().getColSpan() + colSpanDelta * (gapCount+1);
494   }
495
496   @Override
497   public void moveComponent(RadComponent c, int rowDelta, int colDelta, final int rowSpanDelta, final int colSpanDelta) {
498     GridConstraints constraints = c.getConstraints();
499     GridConstraints oldConstraints = (GridConstraints)constraints.clone();
500     constraints.setRow(getNewRow(c, rowDelta));
501     constraints.setColumn(getNewColumn(c, colDelta));
502     constraints.setRowSpan(getNewRowSpan(c, rowSpanDelta));
503     constraints.setColSpan(getNewColSpan(c, colSpanDelta));
504     c.fireConstraintsChanged(oldConstraints);
505   }
506
507   public int getMinCellCount() {
508     return 1;
509   }
510
511   @Override
512   public void addComponentToContainer(RadContainer container, RadComponent component, int index) {
513     MyPropertyChangeListener listener = new MyPropertyChangeListener(component);
514     myListenerMap.put(component, listener);
515     component.addPropertyChangeListener(listener);
516   }
517
518   @Override
519   public void removeComponentFromContainer(final RadContainer container, final RadComponent component) {
520     final MyPropertyChangeListener listener = myListenerMap.get(component);
521     if (listener != null) {
522       component.removePropertyChangeListener(listener);
523       myListenerMap.remove(component);
524     }
525     super.removeComponentFromContainer(container, component);
526   }
527
528   protected void updateConstraints(RadComponent component) {
529     component.getParent().revalidate();
530   }
531
532   private class MyPropertyChangeListener implements PropertyChangeListener {
533     private final RadComponent myComponent;
534
535     public MyPropertyChangeListener(final RadComponent component) {
536       myComponent = component;
537     }
538
539     public void propertyChange(PropertyChangeEvent evt) {
540       if (evt.getPropertyName().equals(RadComponent.PROP_CONSTRAINTS)) {
541         updateConstraints(myComponent);
542       }
543     }
544   }
545 }