bbec27cdb9cd7f1e79d80493aa9eb30b216eb206
[idea/community.git] / platform / platform-api / src / com / intellij / ui / components / panels / HorizontalLayout.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 horizontally.
30  * It allows to add them into the LEFT, CENTER, or RIGHT 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>VerticalLayout</code> class accordingly</b></p>
34  *
35  * @author Sergey.Malenkov
36  * @see VerticalLayout
37  */
38 public final class HorizontalLayout implements LayoutManager2 {
39   public static final String LEFT = "LEFT";
40   public static final String RIGHT = "RIGHT";
41   public static final String CENTER = "CENTER";
42
43   private final ArrayList<Component> myLeft = new ArrayList<>();
44   private final ArrayList<Component> myRight = 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 heights will be set according to the container.
53    *
54    * @param gap horizontal gap between components
55    */
56   public HorizontalLayout(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       horizontal gap between components
66    * @param alignment vertical alignment for components
67    *
68    * @see SwingConstants#TOP
69    * @see SwingConstants#BOTTOM
70    * @see SwingConstants#CENTER
71    */
72   public HorizontalLayout(int gap, int alignment) {
73     myGap = gap;
74     switch (alignment) {
75       case SwingConstants.TOP:
76       case SwingConstants.BOTTOM:
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 || LEFT.equalsIgnoreCase(name)) {
118         myLeft.add(component);
119       }
120       else if (CENTER.equalsIgnoreCase(name)) {
121         myCenter.add(component);
122       }
123       else if (RIGHT.equalsIgnoreCase(name)) {
124         myRight.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     myLeft.remove(component);
135     myRight.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 left = getPreferredSize(myLeft);
153       Dimension right = getPreferredSize(myRight);
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 leftX = 0;
161       if (left != null) {
162         leftX = myGap + layout(myLeft, 0, height, insets);
163       }
164       int rightX = width;
165       if (right != null) {
166         rightX -= right.width;
167       }
168       if (rightX < leftX) {
169         rightX = leftX;
170       }
171       if (center != null) {
172         int centerX = (width - center.width) / 2;
173         if (centerX > leftX) {
174           int centerRightX = centerX + center.width + myGap + myGap;
175           if (centerRightX > rightX) {
176             centerX = rightX - center.width - myGap - myGap;
177           }
178         }
179         if (centerX < leftX) {
180           centerX = leftX;
181         }
182         centerX = myGap + layout(myCenter, centerX, height, insets);
183         if (rightX < centerX) {
184           rightX = centerX;
185         }
186       }
187       if (right != null) {
188         layout(myRight, rightX, height, insets);
189       }
190     }
191   }
192
193   private int layout(ArrayList<Component> list, int x, int height, Insets insets) {
194     for (Component component : list) {
195       if (component.isVisible()) {
196         Dimension size = component.getPreferredSize();
197         int y = 0;
198         if (myAlignment == -1) {
199           size.height = height;
200         }
201         else if (myAlignment != SwingConstants.TOP) {
202           y = height - size.height;
203           if (myAlignment == SwingConstants.CENTER) {
204             y /= 2;
205           }
206         }
207         component.setBounds(x + insets.left, y + insets.top, size.width, size.height);
208         x += size.width + myGap;
209       }
210     }
211     return x;
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.width += gap + size.width;
222     if (result.height < size.height) {
223       result.height = size.height;
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 left = getPreferredSize(myLeft);
241       Dimension right = getPreferredSize(myRight);
242       Dimension center = getPreferredSize(myCenter);
243       Dimension result = join(join(join(null, myGap + myGap, left), myGap + myGap, center), myGap + myGap, right);
244       if (result == null) {
245         result = new Dimension();
246       }
247       else if (aligned && center != null) {
248         int leftWidth = left == null ? 0 : left.width;
249         int rightWidth = right == null ? 0 : right.width;
250         result.width += Math.abs(leftWidth - rightWidth);
251       }
252       JBInsets.addTo(result, container.getInsets());
253       return result;
254     }
255   }
256 }