144708dbfb0e11ff99e9e27352926506278d0506
[idea/community.git] / platform / platform-api / src / com / intellij / ui / components / panels / VerticalLayout.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.ui.components.panels;
17
18 import com.intellij.util.ui.JBInsets;
19
20 import javax.swing.SwingConstants;
21 import java.awt.Component;
22 import java.awt.Container;
23 import java.awt.Dimension;
24 import java.awt.Insets;
25 import java.awt.LayoutManager2;
26 import java.util.ArrayList;
27
28 /**
29  * This class is intended to lay out added components vertically.
30  * It allows to add them into the TOP, CENTER, or BOTTOM group, which are aligned separately.
31  * Every group can contain any amount of components. The specified gap is added between components,
32  * and the double gap is added between groups of components.
33  * <p><b>NB!: this class must be modified together with the <code>HorizontalLayout</code> class accordingly</b></p>
34  *
35  * @author Sergey.Malenkov
36  * @see HorizontalLayout
37  */
38 public final class VerticalLayout implements LayoutManager2 {
39   public static final String TOP = "TOP";
40   public static final String BOTTOM = "BOTTOM";
41   public static final String CENTER = "CENTER";
42
43   private final ArrayList<Component> myTop = new ArrayList<>();
44   private final ArrayList<Component> myBottom = new ArrayList<>();
45   private final ArrayList<Component> myCenter = new ArrayList<>();
46   private final int myAlignment;
47   private final int myGap;
48
49   /**
50    * Creates a layout with the specified gap.
51    * All components will have preferred widths,
52    * but their widths will be set according to the container.
53    *
54    * @param gap vertical gap between components
55    */
56   public VerticalLayout(int gap) {
57     myGap = gap;
58     myAlignment = -1;
59   }
60
61   /**
62    * Creates a layout with the specified gap and vertical alignment.
63    * All components will have preferred sizes.
64    *
65    * @param gap       vertical gap between components
66    * @param alignment horizontal alignment for components
67    *
68    * @see SwingConstants#LEFT
69    * @see SwingConstants#RIGHT
70    * @see SwingConstants#CENTER
71    */
72   public VerticalLayout(int gap, int alignment) {
73     myGap = gap;
74     switch (alignment) {
75       case SwingConstants.LEFT:
76       case SwingConstants.RIGHT:
77       case SwingConstants.CENTER:
78         myAlignment = alignment;
79         break;
80       default:
81         throw new IllegalArgumentException("unsupported alignment: " + alignment);
82     }
83   }
84
85   @Override
86   public void addLayoutComponent(Component component, Object constraints) {
87     if ((constraints == null) || (constraints instanceof String)) {
88       addLayoutComponent((String)constraints, component);
89     }
90     else {
91       throw new IllegalArgumentException("unsupported constraints: " + constraints);
92     }
93   }
94
95   @Override
96   public Dimension maximumLayoutSize(Container target) {
97     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
98   }
99
100   @Override
101   public float getLayoutAlignmentX(Container target) {
102     return .5f;
103   }
104
105   @Override
106   public float getLayoutAlignmentY(Container target) {
107     return .5f;
108   }
109
110   @Override
111   public void invalidateLayout(Container target) {
112   }
113
114   @Override
115   public void addLayoutComponent(String name, Component component) {
116     synchronized (component.getTreeLock()) {
117       if (name == null || TOP.equalsIgnoreCase(name)) {
118         myTop.add(component);
119       }
120       else if (CENTER.equalsIgnoreCase(name)) {
121         myCenter.add(component);
122       }
123       else if (BOTTOM.equalsIgnoreCase(name)) {
124         myBottom.add(component);
125       }
126       else {
127         throw new IllegalArgumentException("unsupported name: " + name);
128       }
129     }
130   }
131
132   @Override
133   public void removeLayoutComponent(Component component) {
134     myTop.remove(component);
135     myBottom.remove(component);
136     myCenter.remove(component);
137   }
138
139   @Override
140   public Dimension preferredLayoutSize(Container container) {
141     return getPreferredSize(container, true);
142   }
143
144   @Override
145   public Dimension minimumLayoutSize(Container container) {
146     return getPreferredSize(container, false);
147   }
148
149   @Override
150   public void layoutContainer(Container container) {
151     synchronized (container.getTreeLock()) {
152       Dimension top = getPreferredSize(myTop);
153       Dimension bottom = getPreferredSize(myBottom);
154       Dimension center = getPreferredSize(myCenter);
155
156       Insets insets = container.getInsets();
157       int width = container.getWidth() - insets.left - insets.right;
158       int height = container.getHeight() - insets.top - insets.bottom;
159
160       int topY = 0;
161       if (top != null) {
162         topY = myGap + layout(myTop, 0, width, insets);
163       }
164       int bottomY = height;
165       if (bottom != null) {
166         bottomY -= bottom.height;
167       }
168       if (bottomY < topY) {
169         bottomY = topY;
170       }
171       if (center != null) {
172         int centerY = (height - center.height) / 2;
173         if (centerY > topY) {
174           int centerBottomY = centerY + center.height + myGap + myGap;
175           if (centerBottomY > bottomY) {
176             centerY = bottomY - center.height - myGap - myGap;
177           }
178         }
179         if (centerY < topY) {
180           centerY = topY;
181         }
182         centerY = myGap + layout(myCenter, centerY, width, insets);
183         if (bottomY < centerY) {
184           bottomY = centerY;
185         }
186       }
187       if (bottom != null) {
188         layout(myBottom, bottomY, width, insets);
189       }
190     }
191   }
192
193   private int layout(ArrayList<Component> list, int y, int width, Insets insets) {
194     for (Component component : list) {
195       if (component.isVisible()) {
196         Dimension size = component.getPreferredSize();
197         int x = 0;
198         if (myAlignment == -1) {
199           size.width = width;
200         }
201         else if (myAlignment != SwingConstants.LEFT) {
202           x = width - size.width;
203           if (myAlignment == SwingConstants.CENTER) {
204             x /= 2;
205           }
206         }
207         component.setBounds(x + insets.left, y + insets.top, size.width, size.height);
208         y += size.height + myGap;
209       }
210     }
211     return y;
212   }
213
214   private static Dimension join(Dimension result, int gap, Dimension size) {
215     if (size == null) {
216       return result;
217     }
218     if (result == null) {
219       return new Dimension(size);
220     }
221     result.height += gap + size.height;
222     if (result.width < size.width) {
223       result.width = size.width;
224     }
225     return result;
226   }
227
228   private Dimension getPreferredSize(ArrayList<Component> list) {
229     Dimension result = null;
230     for (Component component : list) {
231       if (component.isVisible()) {
232         result = join(result, myGap, component.getPreferredSize());
233       }
234     }
235     return result;
236   }
237
238   private Dimension getPreferredSize(Container container, boolean aligned) {
239     synchronized (container.getTreeLock()) {
240       Dimension top = getPreferredSize(myTop);
241       Dimension bottom = getPreferredSize(myBottom);
242       Dimension center = getPreferredSize(myCenter);
243       Dimension result = join(join(join(null, myGap + myGap, top), myGap + myGap, center), myGap + myGap, bottom);
244       if (result == null) {
245         result = new Dimension();
246       }
247       else if (aligned && center != null) {
248         int topHeight = top == null ? 0 : top.height;
249         int bottomHeight = bottom == null ? 0 : bottom.height;
250         result.width += Math.abs(topHeight - bottomHeight);
251       }
252       JBInsets.addTo(result, container.getInsets());
253       return result;
254     }
255   }
256 }