replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / GridBuildUtil.java
1 /*
2  * Copyright 2000-2014 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 package com.intellij.uiDesigner;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.module.Module;
20 import com.intellij.openapi.util.Couple;
21 import com.intellij.uiDesigner.core.GridConstraints;
22 import com.intellij.uiDesigner.core.GridLayoutManager;
23 import com.intellij.uiDesigner.core.Util;
24 import com.intellij.uiDesigner.designSurface.GuiEditor;
25 import com.intellij.uiDesigner.palette.ComponentItem;
26 import com.intellij.uiDesigner.palette.Palette;
27 import com.intellij.uiDesigner.radComponents.*;
28 import com.intellij.uiDesigner.shared.XYLayoutManager;
29
30 import java.awt.*;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Comparator;
34
35 /**
36  * @author yole
37  */
38 public class GridBuildUtil {
39   private static final Logger LOG=Logger.getInstance("#com.intellij.uiDesigner.GridBuildUtil");
40
41   private final static int HORIZONTAL_GRID = 1;
42   private final static int VERTICAL_GRID = 2;
43   private final static int GRID = 3;
44   /**
45    * TODO[anton,vova]: most likely should be equal to "xy grid step" when available
46    */
47   private static final int GRID_TREMOR = 5;
48
49   private GridBuildUtil() {
50   }
51
52   public static void breakGrid(final GuiEditor editor) {
53     final ArrayList<RadComponent> selection = FormEditingUtil.getSelectedComponents(editor);
54     if (selection.size() != 1){
55       return;
56     }
57     if (!(selection.get(0) instanceof RadContainer)) {
58       return;
59     }
60     final RadContainer container = (RadContainer)selection.get(0);
61     if (
62       container instanceof RadScrollPane ||
63       container instanceof RadSplitPane ||
64       container instanceof RadTabbedPane
65     ){
66       return;
67     }
68
69     final RadContainer parent = container.getParent();
70
71     if (parent instanceof RadRootContainer) {
72       editor.getRootContainer().setMainComponentBinding(container.getBinding());
73     }
74
75     // XY can be broken only if its parent is also XY.
76     // In other words, breaking of XY is a deletion of unnecessary intermediate panel
77     if (container.isXY() && !parent.isXY()) {
78       return;
79     }
80
81     if (parent != null && parent.isXY()) {
82       // parent is XY
83       // put the contents of the container into 'parent' and remove 'container'
84
85       final int dx = container.getX();
86       final int dy = container.getY();
87
88       while (container.getComponentCount() > 0) {
89         final RadComponent component = container.getComponent(0);
90         component.shift(dx, dy);
91         parent.addComponent(component);
92       }
93
94       parent.removeComponent(container);
95     }
96     else {
97       // container becomes XY
98       final XYLayoutManager xyLayout = new XYLayoutManagerImpl();
99       container.setLayout(xyLayout);
100       xyLayout.setPreferredSize(container.getSize());
101     }
102
103     editor.refreshAndSave(true);
104   }
105
106   public static void convertToVerticalGrid(final GuiEditor editor){
107     convertToGridImpl(editor, VERTICAL_GRID);
108   }
109
110   public static void convertToHorizontalGrid(final GuiEditor editor){
111     convertToGridImpl(editor, HORIZONTAL_GRID);
112   }
113
114   public static void convertToGrid(final GuiEditor editor){
115     convertToGridImpl(editor, GRID);
116   }
117
118   private static void convertToGridImpl(final GuiEditor editor, final int gridType) {
119     final boolean createNewContainer;
120
121     final RadContainer parent;
122     final RadComponent[] componentsToConvert;
123     {
124       final ArrayList<RadComponent> selection = FormEditingUtil.getSelectedComponents(editor);
125       if (selection.size() == 0) {
126         // root container selected
127         final RadRootContainer rootContainer = editor.getRootContainer();
128         if (rootContainer.getComponentCount() < 2) {
129           // nothing to convert
130           return;
131         }
132
133         componentsToConvert = new RadComponent[rootContainer.getComponentCount()];
134         for (int i = 0; i < componentsToConvert.length; i++) {
135           componentsToConvert[i] = rootContainer.getComponent(i);
136         }
137         parent = rootContainer;
138         createNewContainer = true;
139       }
140       else if (selection.size() == 1 && selection.get(0) instanceof RadContainer) {
141         parent = (RadContainer)selection.get(0);
142         componentsToConvert = new RadComponent[parent.getComponentCount()];
143         for (int i = 0; i < componentsToConvert.length; i++) {
144           componentsToConvert[i] = parent.getComponent(i);
145         }
146         createNewContainer = false;
147       }
148       else {
149         componentsToConvert = selection.toArray(new RadComponent[selection.size()]);
150         parent = selection.get(0).getParent();
151         createNewContainer = true;
152       }
153     }
154
155     if (!parent.isXY()) {
156       // only components in XY can be layed out in grid
157       return;
158     }
159     for (int i = 1; i < componentsToConvert.length; i++) {
160       final RadComponent component = componentsToConvert[i];
161       if (component.getParent() != parent) {
162         return;
163       }
164     }
165
166     final GridLayoutManager gridLayoutManager;
167     if (componentsToConvert.length == 0) {
168       // we convert empty XY panel to grid
169       gridLayoutManager = new GridLayoutManager(1,1);
170     }
171     else {
172       if (gridType == VERTICAL_GRID) {
173         gridLayoutManager = createOneDimensionGrid(componentsToConvert, true);
174       }
175       else if (gridType == HORIZONTAL_GRID) {
176         gridLayoutManager = createOneDimensionGrid(componentsToConvert, false);
177       }
178       else if (gridType == GRID) {
179         gridLayoutManager = createTwoDimensionGrid(componentsToConvert);
180       }
181       else {
182         throw new IllegalArgumentException("invalid grid type: " + gridType);
183       }
184     }
185
186     for (final RadComponent component : componentsToConvert) {
187       if (component instanceof RadContainer) {
188         final LayoutManager layout = ((RadContainer)component).getLayout();
189         if (layout instanceof XYLayoutManager) {
190           ((XYLayoutManager)layout).setPreferredSize(component.getSize());
191         }
192       }
193     }
194
195     if (createNewContainer) {
196       // we should create a new panel
197
198       final Module module = editor.getModule();
199       final ComponentItem panelItem = Palette.getInstance(editor.getProject()).getPanelItem();
200       final RadContainer newContainer = new RadContainer(editor, FormEditingUtil.generateId(editor.getRootContainer()));
201       newContainer.setLayout(gridLayoutManager);
202       newContainer.init(editor, panelItem);
203
204       for (RadComponent componentToConvert : componentsToConvert) {
205         newContainer.addComponent(componentToConvert);
206       }
207
208       final Point topLeftPoint = getTopLeftPoint(componentsToConvert);
209       newContainer.setLocation(topLeftPoint);
210
211       final Point bottomRightPoint = getBottomRightPoint(componentsToConvert);
212       final Dimension size = new Dimension(bottomRightPoint.x - topLeftPoint.x, bottomRightPoint.y - topLeftPoint.y);
213       Util.adjustSize(newContainer.getDelegee(), newContainer.getConstraints(), size);
214       newContainer.getDelegee().setSize(size);
215
216       parent.addComponent(newContainer);
217
218       FormEditingUtil.clearSelection(editor.getRootContainer());
219       newContainer.setSelected(true);
220
221       // restore binding of main component
222       {
223         final String mainComponentBinding = editor.getRootContainer().getMainComponentBinding();
224         if (mainComponentBinding != null && parent instanceof RadRootContainer) {
225           newContainer.setBinding(mainComponentBinding);
226           editor.getRootContainer().setMainComponentBinding(null);
227         }
228       }
229     }
230     else {
231       // convert entire 'parent' to grid
232
233       parent.setLayout(gridLayoutManager);
234
235       FormEditingUtil.clearSelection(editor.getRootContainer());
236       parent.setSelected(true);
237     }
238
239     editor.refreshAndSave(true);
240   }
241
242   private static GridLayoutManager createOneDimensionGrid(final RadComponent[] selection, final boolean isVertical){
243     Arrays.sort(
244       selection,
245       (o1, o2) -> {
246         final Rectangle bounds1 = o1.getBounds();
247         final Rectangle bounds2 = o2.getBounds();
248
249         if (isVertical) {
250           return (bounds1.y + bounds1.height / 2) - (bounds2.y + bounds2.height / 2);
251         }
252         else {
253           return (bounds1.x + bounds1.width / 2) - (bounds2.x + bounds2.width / 2);
254         }
255       }
256     );
257
258     for (int i = 0; i < selection.length; i++) {
259       final RadComponent component = selection[i];
260       final GridConstraints constraints = component.getConstraints();
261       if (isVertical) {
262         constraints.setRow(i);
263         constraints.setColumn(0);
264       }
265       else {
266         constraints.setRow(0);
267         constraints.setColumn(i);
268       }
269       constraints.setRowSpan(1);
270       constraints.setColSpan(1);
271     }
272
273     final GridLayoutManager gridLayoutManager;
274     if (isVertical) {
275       gridLayoutManager = new GridLayoutManager(selection.length, 1);
276     }
277     else {
278       gridLayoutManager = new GridLayoutManager(1, selection.length);
279     }
280     return gridLayoutManager;
281   }
282
283   /**
284    * @param x array of {@code X} coordinates of components that should be layed out in a grid.
285    * This is input/output parameter.
286    *
287    * @param y array of {@code Y} coordinates of components that should be layed out in a grid.
288    * This is input/output parameter.
289    *
290    * @param rowSpans output parameter.
291    *
292    * @param colSpans output parameter.
293    *
294    * @return pair that says how many (rows, columns) are in the composed grid.
295    */
296   public static Couple<Integer> layoutInGrid(
297     final int[] x,
298     final int[] y,
299     final int[] rowSpans,
300     final int[] colSpans
301   ){
302     LOG.assertTrue(x.length == y.length);
303     LOG.assertTrue(y.length == colSpans.length);
304     LOG.assertTrue(colSpans.length == rowSpans.length);
305
306     for (int i = 0; i < x.length; i++) {
307       colSpans[i] = Math.max(colSpans[i], 1);
308       rowSpans[i] = Math.max(rowSpans[i], 1);
309
310       if (colSpans[i] > GRID_TREMOR * 4) {
311         colSpans[i] -= GRID_TREMOR * 2;
312         x[i] += GRID_TREMOR;
313       }
314       if (rowSpans[i] > GRID_TREMOR * 4) {
315         rowSpans[i] -= GRID_TREMOR * 2;
316         y[i] += GRID_TREMOR;
317       }
318     }
319
320
321     return Couple.of(
322       new Integer(Util.eliminate(y, rowSpans, null)),
323       new Integer(Util.eliminate(x, colSpans, null))
324     );
325   }
326
327   private static GridLayoutManager createTwoDimensionGrid(final RadComponent[] selection){
328     final int[] x = new int[selection.length];
329     final int[] y = new int[selection.length];
330     final int[] colSpans = new int[selection.length];
331     final int[] rowSpans = new int[selection.length];
332
333     for (int i = selection.length - 1; i >= 0; i--) {
334       x[i] = selection[i].getX();
335       y[i] = selection[i].getY();
336       rowSpans[i] = selection[i].getHeight();
337       colSpans[i] = selection[i].getWidth();
338     }
339
340     final Couple<Integer> pair = layoutInGrid(x, y, rowSpans, colSpans);
341     for (int i = 0; i < selection.length; i++) {
342       final RadComponent component = selection[i];
343       final GridConstraints constraints = component.getConstraints();
344
345       constraints.setRow(y[i]);
346       constraints.setRowSpan(rowSpans[i]);
347
348       constraints.setColumn(x[i]);
349       constraints.setColSpan(colSpans[i]);
350     }
351
352     return new GridLayoutManager(pair.first.intValue(), pair.second.intValue());
353   }
354
355   /**
356    * Find top left point of component group, i.e. (minimum X of all components; minimum Y of all components)
357    * @param components array should contain at least one element
358    */
359   private static Point getTopLeftPoint(final RadComponent[] components){
360     LOG.assertTrue(components.length > 0);
361
362     final Point point = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
363     for (final RadComponent component : components) {
364       point.x = Math.min(component.getX(), point.x);
365       point.y = Math.min(component.getY(), point.y);
366     }
367
368     return point;
369   }
370
371   /**
372    * Find bottom right point of component group, i.e. (maximum (x + width) of all components; maximum (y + height) of all components)
373    * @param components array should contain at least one element
374    */
375   private static Point getBottomRightPoint(final RadComponent[] components){
376     LOG.assertTrue(components.length > 0);
377
378     final Point point = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
379     for (final RadComponent component : components) {
380       point.x = Math.max(component.getX() + component.getWidth(), point.x);
381       point.y = Math.max(component.getY() + component.getHeight(), point.y);
382     }
383
384     return point;
385   }
386 }