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