IDEA-34482
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / radComponents / RadGridBagLayoutManager.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.project.Project;
20 import com.intellij.uiDesigner.UIFormXmlConstants;
21 import com.intellij.uiDesigner.XmlWriter;
22 import com.intellij.uiDesigner.compiler.GridBagConverter;
23 import com.intellij.uiDesigner.core.GridConstraints;
24 import com.intellij.uiDesigner.core.Util;
25 import com.intellij.uiDesigner.designSurface.ComponentDropLocation;
26 import com.intellij.uiDesigner.designSurface.FirstComponentInsertLocation;
27 import com.intellij.uiDesigner.propertyInspector.Property;
28 import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
29 import com.intellij.uiDesigner.propertyInspector.PropertyRenderer;
30 import com.intellij.uiDesigner.propertyInspector.editors.PrimitiveTypeEditor;
31 import com.intellij.uiDesigner.propertyInspector.properties.AbstractInsetsProperty;
32 import com.intellij.uiDesigner.propertyInspector.properties.AbstractIntProperty;
33 import com.intellij.uiDesigner.propertyInspector.properties.HorzAlignProperty;
34 import com.intellij.uiDesigner.propertyInspector.properties.VertAlignProperty;
35 import com.intellij.uiDesigner.propertyInspector.renderers.LabelPropertyRenderer;
36 import com.intellij.uiDesigner.snapShooter.SnapshotContext;
37 import com.intellij.util.IncorrectOperationException;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import javax.swing.*;
42 import java.awt.*;
43
44 /**
45  * @author yole
46  */
47 public class RadGridBagLayoutManager extends RadAbstractGridLayoutManager {
48   private int myLastSnapshotRow = -1;
49   private int myLastSnapshotCol = -1;
50   private final int[] mySnapshotXMax = new int[512];
51   private final int[] mySnapshotYMax = new int[512];
52
53   @Override
54   public String getName() {
55     return UIFormXmlConstants.LAYOUT_GRIDBAG;
56   }
57
58   @Override
59   public LayoutManager createLayout() {
60     return new GridBagLayout();
61   }
62
63   @Override
64   public void changeContainerLayout(RadContainer container) throws IncorrectOperationException {
65     if (container.getLayoutManager().isGrid()) {
66       // preprocess: store weights in GridBagConstraints
67       RadAbstractGridLayoutManager grid = container.getGridLayoutManager();
68       for (RadComponent c : container.getComponents()) {
69         GridBagConstraints gbc = GridBagConverter.getGridBagConstraints(c);
70         if (grid.canCellGrow(container, false, c.getConstraints().getColumn())) {
71           gbc.weightx = 1.0;
72         }
73         if (grid.canCellGrow(container, true, c.getConstraints().getRow())) {
74           gbc.weighty = 1.0;
75         }
76         c.setCustomLayoutConstraints(gbc);
77       }
78     }
79     super.changeContainerLayout(container);
80   }
81
82   public void writeChildConstraints(final XmlWriter writer, final RadComponent child) {
83     writeGridConstraints(writer, child);
84     if (child.getCustomLayoutConstraints() instanceof GridBagConstraints) {
85       GridBagConstraints gbc = (GridBagConstraints)child.getCustomLayoutConstraints();
86       writer.startElement(UIFormXmlConstants.ELEMENT_GRIDBAG);
87       try {
88         if (!gbc.insets.equals(new Insets(0, 0, 0, 0))) {
89           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TOP, gbc.insets.top);
90           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_LEFT, gbc.insets.left);
91           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_BOTTOM, gbc.insets.bottom);
92           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_RIGHT, gbc.insets.right);
93         }
94         writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_WEIGHTX, gbc.weightx);
95         writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_WEIGHTY, gbc.weighty);
96         if (gbc.ipadx != 0) {
97           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_IPADX, gbc.ipadx);
98         }
99         if (gbc.ipady != 0) {
100           writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_IPADY, gbc.ipady);
101         }
102       }
103       finally {
104         writer.endElement();
105       }
106     }
107   }
108
109   @Override
110   public void addComponentToContainer(final RadContainer container, final RadComponent component, final int index) {
111     super.addComponentToContainer(container, component, index);
112     GridBagConstraints gbc = GridBagConverter.getGridBagConstraints(component);
113     component.setCustomLayoutConstraints(gbc);
114     container.getDelegee().add(component.getDelegee(), gbc, index);
115   }
116
117   @Override
118   public void refresh(RadContainer container) {
119     checkEmptyCells(container);
120   }
121
122   private static final int MINIMUM_GRID_SIZE = 15;
123
124   private static void checkEmptyCells(RadContainer container) {
125     JComponent jComponent = container.getDelegee();
126     GridBagLayout layout = (GridBagLayout)jComponent.getLayout();
127     Dimension oldSize = jComponent.getSize();
128
129     // clear cells
130     layout.columnWidths = null;
131     layout.rowHeights = null;
132
133     // calculate layout fields
134     jComponent.setSize(500, 400);
135     jComponent.doLayout();
136     jComponent.setSize(oldSize);
137
138     int[][] dimensions = layout.getLayoutDimensions();
139
140     // check empty columns
141     int[] columnWidths = dimensions[0];
142     boolean doLayoutColumns = false;
143
144     for (int i = 0; i < columnWidths.length; i++) {
145       if (columnWidths[i] == 0) {
146         columnWidths[i] = MINIMUM_GRID_SIZE;
147         doLayoutColumns = true;
148       }
149     }
150
151     // check empty rows
152     int[] rowHeights = dimensions[1];
153     boolean doLayoutRows = false;
154
155     for (int i = 0; i < rowHeights.length; i++) {
156       if (rowHeights[i] == 0) {
157         rowHeights[i] = MINIMUM_GRID_SIZE;
158         doLayoutRows = true;
159       }
160     }
161
162     // apply changes
163     if (doLayoutColumns) {
164       layout.columnWidths = columnWidths;
165     }
166     if (doLayoutRows) {
167       layout.rowHeights = rowHeights;
168     }
169     if (doLayoutColumns || doLayoutRows) {
170       jComponent.doLayout();
171     }
172   }
173
174   @Override
175   public Property[] getContainerProperties(final Project project) {
176     return Property.EMPTY_ARRAY;
177   }
178
179   @Override
180   public Property[] getComponentProperties(final Project project, final RadComponent component) {
181     return new Property[]{
182       new HorzAlignProperty(),
183       new VertAlignProperty(),
184       new ComponentInsetsProperty(),
185       new WeightProperty(true),
186       new WeightProperty(false),
187       new IPadProperty(true),
188       new IPadProperty(false)
189     };
190   }
191
192   private static GridBagLayout getGridBag(RadContainer container) {
193     return (GridBagLayout)container.getLayout();
194   }
195
196   @Override
197   public int getGridRowCount(RadContainer container) {
198     int[][] layoutDimensions = getGridBag(container).getLayoutDimensions();
199     return layoutDimensions[1].length;
200   }
201
202   @Override
203   public int getGridColumnCount(RadContainer container) {
204     int[][] layoutDimensions = getGridBag(container).getLayoutDimensions();
205     return layoutDimensions[0].length;
206   }
207
208   @Override
209   public int[] getHorizontalGridLines(RadContainer container) {
210     return getGridLines(container, 1, 1);
211   }
212
213   @Override
214   public int[] getVerticalGridLines(RadContainer container) {
215     return getGridLines(container, 0, 1);
216   }
217
218   @Override
219   public int[] getGridCellCoords(RadContainer container, boolean isRow) {
220     return getGridLines(container, isRow ? 1 : 0, 0);
221   }
222
223   @Override
224   public int[] getGridCellSizes(RadContainer container, boolean isRow) {
225     int[][] layoutDimensions = getGridBag(container).getLayoutDimensions();
226     return layoutDimensions[isRow ? 1 : 0];
227   }
228
229   private static int[] getGridLines(final RadContainer container, final int rowColIndex, final int delta) {
230     final GridBagLayout gridBag = getGridBag(container);
231     Point layoutOrigin = gridBag.getLayoutOrigin();
232     int[][] layoutDimensions = gridBag.getLayoutDimensions();
233     int[] result = new int[layoutDimensions[rowColIndex].length + delta];
234     if (result.length > 0) {
235       result[0] = (rowColIndex == 0) ? layoutOrigin.x : layoutOrigin.y;
236       for (int i = 1; i < result.length; i++) {
237         result[i] = result[i - 1] + layoutDimensions[rowColIndex][i - 1];
238       }
239     }
240     return result;
241   }
242
243   @NotNull
244   @Override
245   public ComponentDropLocation getDropLocation(@NotNull RadContainer container, @Nullable final Point location) {
246     if (getGridRowCount(container) == 0 && getGridColumnCount(container) == 0) {
247       return new FirstComponentInsertLocation(container, container.getBounds(), 0, 0);
248     }
249     return super.getDropLocation(container, location);
250   }
251
252   public void copyGridSection(final RadContainer source, final RadContainer destination, final Rectangle rc) {
253     destination.setLayout(new GridBagLayout());
254   }
255
256   @Override
257   protected void updateConstraints(final RadComponent component) {
258     GridBagLayout layout = (GridBagLayout)component.getParent().getLayout();
259     GridBagConstraints gbc = GridBagConverter.getGridBagConstraints(component);
260     layout.setConstraints(component.getDelegee(), gbc);
261     super.updateConstraints(component);
262   }
263
264   @Override
265   public boolean isGridDefinedByComponents() {
266     return true;
267   }
268
269   @Override
270   public boolean canResizeCells() {
271     return false;
272   }
273
274   @Override
275   public boolean canCellGrow(RadContainer container, boolean isRow, int i) {
276     GridBagLayout gridBag = getGridBag(container);
277     double[][] weights = gridBag.getLayoutWeights();
278     if (weights != null) {
279       double[] cellWeights = weights[isRow ? 1 : 0];
280       return i >= 0 && i < cellWeights.length && cellWeights[i] >= 0.1;
281     }
282     return false;
283   }
284
285   @Override
286   public void setChildDragging(RadComponent child, boolean dragging) {
287     // do nothing here - setting visible to false would cause exceptions
288   }
289
290   @Override
291   public void createSnapshotLayout(final SnapshotContext context,
292                                    final JComponent parent,
293                                    final RadContainer container,
294                                    final LayoutManager layout) {
295     container.setLayout(new GridBagLayout());
296   }
297
298   public static Dimension getGridBagSize(final JComponent parent) {
299     GridBagLayout gridBag = (GridBagLayout)parent.getLayout();
300     gridBag.layoutContainer(parent);
301     int[][] layoutDimensions = gridBag.getLayoutDimensions();
302
303     int rowCount = layoutDimensions[1].length;
304     int colCount = layoutDimensions[0].length;
305
306     // account for invisible components
307     for (Component component : parent.getComponents()) {
308       final GridBagConstraints constraints = gridBag.getConstraints(component);
309       colCount = Math.max(colCount, constraints.gridx + constraints.gridwidth);
310       rowCount = Math.max(rowCount, constraints.gridy + constraints.gridheight);
311     }
312
313     return new Dimension(colCount, rowCount);
314   }
315
316   @Override
317   public void addSnapshotComponent(final JComponent parent,
318                                    final JComponent child,
319                                    final RadContainer container,
320                                    final RadComponent component) {
321     Dimension gridBagSize = getGridBagSize(parent);
322
323     // logic copied from GridBagLayout.java
324
325     GridBagLayout gridBag = (GridBagLayout)parent.getLayout();
326     final GridBagConstraints constraints = gridBag.getConstraints(child);
327
328     int curX = constraints.gridx;
329     int curY = constraints.gridy;
330     int curWidth = constraints.gridwidth;
331     int curHeight = constraints.gridheight;
332     int px;
333     int py;
334
335     /* If x or y is negative, then use relative positioning: */
336     if (curX < 0 && curY < 0) {
337       if (myLastSnapshotRow >= 0) {
338         curY = myLastSnapshotRow;
339       }
340       else if (myLastSnapshotCol >= 0) {
341         curX = myLastSnapshotCol;
342       }
343       else {
344         curY = 0;
345       }
346     }
347
348     if (curX < 0) {
349       if (curHeight <= 0) {
350         curHeight += gridBagSize.height - curY;
351         if (curHeight < 1) {
352           curHeight = 1;
353         }
354       }
355
356       px = 0;
357       for (int i = curY; i < (curY + curHeight); i++) {
358         px = Math.max(px, mySnapshotXMax[i]);
359       }
360
361       curX = px - curX - 1;
362       if (curX < 0) {
363         curX = 0;
364       }
365     }
366     else if (curY < 0) {
367       if (curWidth <= 0) {
368         curWidth += gridBagSize.width - curX;
369         if (curWidth < 1) {
370           curWidth = 1;
371         }
372       }
373
374       py = 0;
375       for (int i = curX; i < (curX + curWidth); i++) {
376         py = Math.max(py, mySnapshotYMax[i]);
377       }
378
379       curY = py - curY - 1;
380       if (curY < 0) {
381         curY = 0;
382       }
383     }
384
385     if (curWidth <= 0) {
386       curWidth += gridBagSize.width - curX;
387       if (curWidth < 1) {
388         curWidth = 1;
389       }
390     }
391
392     if (curHeight <= 0) {
393       curHeight += gridBagSize.height - curY;
394       if (curHeight < 1) {
395         curHeight = 1;
396       }
397     }
398
399     /* Adjust xMax and yMax */
400     for (int i = curX; i < (curX + curWidth); i++) {
401       mySnapshotYMax[i] = curY + curHeight;
402     }
403     for (int i = curY; i < (curY + curHeight); i++) {
404       mySnapshotXMax[i] = curX + curWidth;
405     }
406
407     /* Make negative sizes start a new row/column */
408     if (constraints.gridheight == 0 && constraints.gridwidth == 0) {
409       myLastSnapshotRow = myLastSnapshotCol = -1;
410     }
411     if (constraints.gridheight == 0 && myLastSnapshotRow < 0) {
412       myLastSnapshotCol = curX + curWidth;
413     }
414     else if (constraints.gridwidth == 0 && myLastSnapshotCol < 0) {
415       myLastSnapshotRow = curY + curHeight;
416     }
417
418     component.getConstraints().setColumn(curX);
419     component.getConstraints().setRow(curY);
420     component.getConstraints().setColSpan(curWidth);
421     component.getConstraints().setRowSpan(curHeight);
422
423     if (constraints.weightx >= 1.0) {
424       component.getConstraints().setHSizePolicy(GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_WANT_GROW);
425     }
426     else {
427       component.getConstraints().setHSizePolicy(0);
428     }
429     if (constraints.weighty >= 1.0) {
430       component.getConstraints().setVSizePolicy(GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_WANT_GROW);
431     }
432     else {
433       component.getConstraints().setVSizePolicy(0);
434     }
435     if (constraints.insets.right == 0 && constraints.insets.top == 0 && constraints.insets.bottom == 0) {
436       component.getConstraints().setIndent(constraints.insets.left / Util.DEFAULT_INDENT);
437     }
438
439     component.getConstraints().setAnchor(convertAnchor(constraints));
440     component.getConstraints().setFill(convertFill(constraints));
441     component.setCustomLayoutConstraints(constraints.clone());
442     container.addComponent(component);
443   }
444
445   private static int convertAnchor(final GridBagConstraints gbc) {
446     switch (gbc.anchor) {
447       case GridBagConstraints.NORTHWEST:
448         return GridConstraints.ANCHOR_NORTHWEST;
449       case GridBagConstraints.NORTH:
450         return GridConstraints.ANCHOR_NORTH;
451       case GridBagConstraints.NORTHEAST:
452         return GridConstraints.ANCHOR_NORTHEAST;
453       case GridBagConstraints.EAST:
454         return GridConstraints.ANCHOR_EAST;
455       case GridBagConstraints.SOUTHEAST:
456         return GridConstraints.ANCHOR_SOUTHEAST;
457       case GridBagConstraints.SOUTH:
458         return GridConstraints.ANCHOR_SOUTH;
459       case GridBagConstraints.SOUTHWEST:
460         return GridConstraints.ANCHOR_SOUTHWEST;
461       default:
462         return GridConstraints.ANCHOR_WEST;
463     }
464   }
465
466   private static int convertFill(final GridBagConstraints gbc) {
467     switch (gbc.fill) {
468       case GridBagConstraints.HORIZONTAL:
469         return GridConstraints.FILL_HORIZONTAL;
470       case GridBagConstraints.VERTICAL:
471         return GridConstraints.FILL_VERTICAL;
472       case GridBagConstraints.BOTH:
473         return GridConstraints.FILL_BOTH;
474       default:
475         return GridConstraints.FILL_NONE;
476     }
477   }
478
479   private static class ComponentInsetsProperty extends AbstractInsetsProperty<RadComponent> {
480     public ComponentInsetsProperty() {
481       super(null, "Insets");
482     }
483
484     public Insets getValue(final RadComponent component) {
485       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
486         final GridBagConstraints gbc = (GridBagConstraints)component.getCustomLayoutConstraints();
487         return gbc.insets;
488       }
489       return new Insets(0, 0, 0, 0);
490     }
491
492     protected void setValueImpl(final RadComponent component, final Insets value) throws Exception {
493       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
494         final GridBagConstraints cellConstraints = (GridBagConstraints)component.getCustomLayoutConstraints();
495         cellConstraints.insets = value;
496
497         GridBagLayout layout = (GridBagLayout)component.getParent().getLayout();
498         GridBagConstraints gbc = (GridBagConstraints)layout.getConstraints(component.getDelegee()).clone();
499         gbc.insets = value;
500         layout.setConstraints(component.getDelegee(), gbc);
501       }
502     }
503
504     @Override
505     public boolean isModified(final RadComponent component) {
506       return !getValue(component).equals(new Insets(0, 0, 0, 0));
507     }
508
509     @Override
510     public void resetValue(final RadComponent component) throws Exception {
511       setValue(component, new Insets(0, 0, 0, 0));
512     }
513   }
514
515   private static class WeightProperty extends Property<RadComponent, Double> {
516     private final boolean myIsWeightX;
517     private LabelPropertyRenderer<Double> myRenderer;
518     private PropertyEditor<Double> myEditor;
519
520     public WeightProperty(final boolean isWeightX) {
521       super(null, isWeightX ? "Weight X" : "Weight Y");
522       myIsWeightX = isWeightX;
523     }
524
525     public Double getValue(final RadComponent component) {
526       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
527         GridBagConstraints gbc = (GridBagConstraints)component.getCustomLayoutConstraints();
528         return myIsWeightX ? gbc.weightx : gbc.weighty;
529       }
530       return 0.0;
531     }
532
533     protected void setValueImpl(final RadComponent component, final Double value) throws Exception {
534       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
535         GridBagConstraints gbc = (GridBagConstraints)component.getCustomLayoutConstraints();
536         if (myIsWeightX) {
537           gbc.weightx = value.doubleValue();
538         }
539         else {
540           gbc.weighty = value.doubleValue();
541         }
542         ((GridBagLayout)component.getParent().getLayout()).setConstraints(component.getDelegee(), gbc);
543       }
544     }
545
546     @NotNull
547     public PropertyRenderer<Double> getRenderer() {
548       if (myRenderer == null) {
549         myRenderer = new LabelPropertyRenderer<Double>();
550       }
551       return myRenderer;
552     }
553
554     public PropertyEditor<Double> getEditor() {
555       if (myEditor == null) {
556         myEditor = new PrimitiveTypeEditor<Double>(Double.class);
557       }
558       return myEditor;
559     }
560
561     @Override
562     public boolean isModified(final RadComponent component) {
563       return !(new Double(0.0).equals(getValue(component)));
564     }
565
566     @Override
567     public void resetValue(final RadComponent component) throws Exception {
568       setValue(component, 0.0);
569     }
570   }
571
572   private static class IPadProperty extends AbstractIntProperty<RadComponent> {
573     private final boolean myIsIpadX;
574
575     public IPadProperty(final boolean isIpadX) {
576       super(null, isIpadX ? "Ipad X" : "Ipad Y", 0);
577       myIsIpadX = isIpadX;
578     }
579
580     public Integer getValue(final RadComponent component) {
581       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
582         GridBagConstraints gbc = (GridBagConstraints)component.getCustomLayoutConstraints();
583         return myIsIpadX ? gbc.ipadx : gbc.ipady;
584       }
585       return 0;
586     }
587
588     protected void setValueImpl(final RadComponent component, final Integer value) throws Exception {
589       if (component.getCustomLayoutConstraints() instanceof GridBagConstraints) {
590         GridBagConstraints gbc = (GridBagConstraints)component.getCustomLayoutConstraints();
591         if (myIsIpadX) {
592           gbc.ipadx = value.intValue();
593         }
594         else {
595           gbc.ipady = value.intValue();
596         }
597         ((GridBagLayout)component.getParent().getLayout()).setConstraints(component.getDelegee(), gbc);
598       }
599     }
600   }
601 }