Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
LabelledLayout |
|
| 2.52;2.52 | ||||
Seperator |
|
| 2.52;2.52 |
1 | /* $Id: LabelledLayout.java 19034 2011-02-13 18:12:44Z bobtarling $ | |
2 | ***************************************************************************** | |
3 | * Copyright (c) 2009 Contributors - see below | |
4 | * All rights reserved. This program and the accompanying materials | |
5 | * are made available under the terms of the Eclipse Public License v1.0 | |
6 | * which accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * bobtarling | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 2008-2009 The Regents of the University of California. All | |
17 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
18 | // software and its documentation without fee, and without a written | |
19 | // agreement is hereby granted, provided that the above copyright notice | |
20 | // and this paragraph appear in all copies. This software program and | |
21 | // documentation are copyrighted by The Regents of the University of | |
22 | // California. The software program and documentation are supplied "AS | |
23 | // IS", without any accompanying services from The Regents. The Regents | |
24 | // does not warrant that the operation of the program will be | |
25 | // uninterrupted or error-free. The end-user understands that the program | |
26 | // was developed for research purposes and is advised not to rely | |
27 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
28 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
29 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
30 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
31 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
32 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
33 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
35 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
36 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
37 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
38 | ||
39 | package org.argouml.uml.ui; | |
40 | ||
41 | import java.awt.Component; | |
42 | import java.awt.Container; | |
43 | import java.awt.Dimension; | |
44 | import java.awt.Insets; | |
45 | import java.awt.LayoutManager; | |
46 | import java.util.ArrayList; | |
47 | import javax.swing.JComboBox; | |
48 | import javax.swing.JLabel; | |
49 | import javax.swing.JPanel; | |
50 | import javax.swing.JToolBar; | |
51 | import javax.swing.UIManager; | |
52 | ||
53 | /** | |
54 | * This layout manager lines up components in 2 columns. All JLabels | |
55 | * are the first column and any component the JLabel is registered | |
56 | * with is in a second column next to the label. <p> | |
57 | * | |
58 | * Components are sized automatically to fill available space in the container | |
59 | * when it is resized. <p> | |
60 | * | |
61 | * All JLabel widths will be the largest of the JLabel preferred widths (unless | |
62 | * the container is too narrow). <p> | |
63 | * | |
64 | * The components will take up any left over width unless they are | |
65 | * restricted themselves by a maximum width. <p> | |
66 | * | |
67 | * The height of each component is either fixed or will resize to use up any | |
68 | * available space in the container. Whether a components height is resizable | |
69 | * is determined by checking whether the preferred height of that component is | |
70 | * greater then its minimum height. This is the case for components such as | |
71 | * JList which would require to expand to show the maximum number or items. <p> | |
72 | * | |
73 | * If a component is not to have its height resized then its preferred | |
74 | * height and minimum height should be the same. This is the case for | |
75 | * components such as JTextField and JComboBox* which should always stay the | |
76 | * same height. <p> | |
77 | * | |
78 | * [There is known bug in JRE5 where the prefered height and minimum height of | |
79 | * a JComboBox can differ. LabelledLayout has coded a workaround for this bug. | |
80 | * See - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6255154 ] <p> | |
81 | * | |
82 | * LabelledLayout can show multiple panels of label/component | |
83 | * pairs. The seperation of these panels is indicated by adding a | |
84 | * Seperator component to the container. Labelled layout starts | |
85 | * a new panel when detecting this Seperator. <p> | |
86 | * | |
87 | * When there are multiple panels, each panel is given equal width. | |
88 | * The width restriction of JLabels and components described above are then | |
89 | * dependent on panel width rather than container width. | |
90 | * | |
91 | * @author Bob Tarling | |
92 | */ | |
93 | public class LabelledLayout implements LayoutManager, java.io.Serializable { | |
94 | ||
95 | private static final long serialVersionUID = -5596655602155151443L; | |
96 | ||
97 | /** | |
98 | * This is the horizontal gap (in pixels) which specifies the space | |
99 | * between sections. They can be changed at any time. | |
100 | * This should be a non negative integer. | |
101 | * | |
102 | * @serial | |
103 | * @see #getHgap() | |
104 | * @see #setHgap(int) | |
105 | */ | |
106 | private int hgap; | |
107 | ||
108 | /** | |
109 | * This is the vertical gap (in pixels) which specifies the space | |
110 | * between rows. They can be changed at any time. | |
111 | * This should be a non negative integer. | |
112 | * | |
113 | * @serial | |
114 | * @see #getVgap() | |
115 | * @see #setVgap(int) | |
116 | */ | |
117 | private int vgap; | |
118 | ||
119 | private boolean ignoreSplitters; | |
120 | ||
121 | /** | |
122 | * Construct a new LabelledLayout. | |
123 | */ | |
124 | 4089 | public LabelledLayout() { |
125 | 4089 | ignoreSplitters = false; |
126 | 4089 | hgap = 0; |
127 | 4089 | vgap = 0; |
128 | 4089 | } |
129 | ||
130 | /** | |
131 | * Construct a new LabelledLayout. | |
132 | */ | |
133 | 0 | public LabelledLayout(boolean ignoreSplitters) { |
134 | 0 | this.ignoreSplitters = ignoreSplitters; |
135 | 0 | this.hgap = 0; |
136 | 0 | this.vgap = 0; |
137 | 0 | } |
138 | ||
139 | /** | |
140 | * Construct a new horizontal LabelledLayout with the specified | |
141 | * cell spacing. | |
142 | * @param hgap The horizontal gap between components | |
143 | * @param vgap The vertical gap between components | |
144 | */ | |
145 | 0 | public LabelledLayout(int hgap, int vgap) { |
146 | 0 | this.ignoreSplitters = false; |
147 | 0 | this.hgap = hgap; |
148 | 0 | this.vgap = vgap; |
149 | 0 | } |
150 | ||
151 | /** | |
152 | * Adds the specified component with the specified name to the | |
153 | * layout. This is included to satisfy the LayoutManager interface | |
154 | * but is not actually used in this layout implementation. | |
155 | * | |
156 | * @param name the name of the component | |
157 | * @param comp the component to be added | |
158 | */ | |
159 | public void addLayoutComponent(String name, Component comp) { | |
160 | 0 | } |
161 | ||
162 | /** | |
163 | * Removes the specified component from | |
164 | * the layout. This is included to satisfy the LayoutManager | |
165 | * interface but is not actually used in this layout | |
166 | * implementation. | |
167 | * | |
168 | * @param comp the component | |
169 | */ | |
170 | public void removeLayoutComponent(Component comp) { | |
171 | 0 | } |
172 | ||
173 | /** | |
174 | * Determines the preferred size of the container argument using | |
175 | * this labelled layout. The preferred size is that all child | |
176 | * components are in one section at their own preferred size with | |
177 | * gaps and border indents. | |
178 | */ | |
179 | public Dimension preferredLayoutSize(Container parent) { | |
180 | 0 | synchronized (parent.getTreeLock()) { |
181 | 0 | final Insets insets = parent.getInsets(); |
182 | 0 | int preferredWidth = 0; |
183 | 0 | int preferredHeight = 0; |
184 | 0 | int widestLabel = 0; |
185 | ||
186 | 0 | final int componentCount = parent.getComponentCount(); |
187 | 0 | for (int i = 0; i < componentCount; ++i) { |
188 | 0 | Component childComp = parent.getComponent(i); |
189 | 0 | if (childComp.isVisible() |
190 | && !(childComp instanceof Seperator)) { | |
191 | 0 | int childHeight = getPreferredHeight(childComp); |
192 | 0 | if (childComp instanceof JLabel) { |
193 | 0 | final JLabel jlabel = (JLabel) childComp; |
194 | 0 | widestLabel = |
195 | Math.max(widestLabel, getPreferredWidth(jlabel)); | |
196 | 0 | childComp = jlabel.getLabelFor(); |
197 | 0 | final int childWidth = getPreferredWidth(childComp); |
198 | 0 | preferredWidth = |
199 | Math.max(preferredWidth, childWidth); | |
200 | ||
201 | 0 | childHeight = |
202 | Math.min(childHeight, getPreferredHeight(jlabel)); | |
203 | } | |
204 | 0 | preferredHeight += childHeight + this.vgap; |
205 | } | |
206 | } | |
207 | 0 | preferredWidth += insets.left + widestLabel + insets.right; |
208 | 0 | preferredHeight += insets.top + insets.bottom; |
209 | 0 | return new Dimension( |
210 | insets.left + widestLabel + preferredWidth + insets.right, | |
211 | preferredHeight); | |
212 | 0 | } |
213 | } | |
214 | ||
215 | /** | |
216 | * Required by LayoutManager. | |
217 | * | |
218 | * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container) | |
219 | */ | |
220 | public Dimension minimumLayoutSize(Container parent) { | |
221 | 0 | synchronized (parent.getTreeLock()) { |
222 | 0 | final Insets insets = parent.getInsets(); |
223 | 0 | int minimumHeight = insets.top + insets.bottom; |
224 | ||
225 | 0 | final int componentCount = parent.getComponentCount(); |
226 | 0 | for (int i = 0; i < componentCount; ++i) { |
227 | 0 | Component childComp = parent.getComponent(i); |
228 | 0 | if (childComp instanceof JLabel) { |
229 | 0 | final JLabel jlabel = (JLabel) childComp; |
230 | 0 | childComp = jlabel.getLabelFor(); |
231 | ||
232 | 0 | final int childHeight = Math.max( |
233 | getMinimumHeight(childComp), | |
234 | getMinimumHeight(jlabel)); | |
235 | 0 | minimumHeight += childHeight + this.vgap; |
236 | } | |
237 | } | |
238 | 0 | return new Dimension(0, minimumHeight); |
239 | 0 | } |
240 | } | |
241 | ||
242 | /** | |
243 | * @see java.awt.LayoutManager#layoutContainer(java.awt.Container) | |
244 | */ | |
245 | public void layoutContainer(Container parent) { | |
246 | 3804 | synchronized (parent.getTreeLock()) { |
247 | 3804 | int sectionX = parent.getInsets().left; |
248 | ||
249 | 3804 | final ArrayList<Component> components = new ArrayList<Component>(); |
250 | 3804 | final int sectionCount = getSectionCount(parent); |
251 | 3804 | final int sectionWidth = getSectionWidth(parent, sectionCount); |
252 | 3804 | int sectionNo = 0; |
253 | 50028 | for (int i = 0; i < parent.getComponentCount(); ++i) { |
254 | 46224 | final Component childComp = parent.getComponent(i); |
255 | 46224 | if (childComp instanceof Seperator) { |
256 | 1800 | if (!this.ignoreSplitters) { |
257 | 1800 | layoutSection( |
258 | parent, | |
259 | sectionX, | |
260 | sectionWidth, | |
261 | components, | |
262 | sectionNo++); | |
263 | 1800 | sectionX += sectionWidth + this.hgap; |
264 | 1800 | components.clear(); |
265 | } | |
266 | } else { | |
267 | 44424 | components.add(parent.getComponent(i)); |
268 | } | |
269 | } | |
270 | 3804 | layoutSection( |
271 | parent, | |
272 | sectionX, | |
273 | sectionWidth, | |
274 | components, | |
275 | sectionNo); | |
276 | 3804 | } |
277 | 3804 | } |
278 | ||
279 | /** Determine the number of sections. There is only ever one | |
280 | * section if oriented vertically. If oriented horizontally the | |
281 | * number of sections is deduced from the number of Splitters in | |
282 | * the parent container. | |
283 | */ | |
284 | private int getSectionCount(Container parent) { | |
285 | 3804 | int sectionCount = 1; |
286 | 3804 | final int componentCount = parent.getComponentCount(); |
287 | 3804 | if (!ignoreSplitters) { |
288 | 50028 | for (int i = 0; i < componentCount; ++i) { |
289 | 46224 | if (parent.getComponent(i) instanceof Seperator) { |
290 | 1800 | ++sectionCount; |
291 | } | |
292 | } | |
293 | } | |
294 | 3804 | return sectionCount; |
295 | } | |
296 | ||
297 | /** | |
298 | * Determine the width of each section from the section count. | |
299 | * This is the working width minus the gaps between sections. This | |
300 | * result is then divided equally by the section count. | |
301 | */ | |
302 | private int getSectionWidth(Container parent, int sectionCount) { | |
303 | 3804 | return (getUsableWidth(parent) - (sectionCount - 1) * this.hgap) |
304 | / sectionCount; | |
305 | } | |
306 | ||
307 | /** | |
308 | * Determine the usable width of the parent. | |
309 | * This is the full width minus any borders. | |
310 | */ | |
311 | private int getUsableWidth(Container parent) { | |
312 | 3804 | final Insets insets = parent.getInsets(); |
313 | 3804 | return parent.getWidth() - (insets.left + insets.right); |
314 | } | |
315 | ||
316 | /** | |
317 | * Layout a single section | |
318 | */ | |
319 | private void layoutSection( | |
320 | final Container parent, | |
321 | final int sectionX, | |
322 | final int sectionWidth, | |
323 | final ArrayList components, | |
324 | final int sectionNo) { | |
325 | 5604 | final ArrayList<Integer> rowHeights = new ArrayList<Integer>(); |
326 | ||
327 | 5604 | final int componentCount = components.size(); |
328 | 5604 | if (componentCount == 0) { |
329 | 0 | return; |
330 | } | |
331 | ||
332 | 5604 | int labelWidth = 0; |
333 | 5604 | int unknownHeightCount = 0; |
334 | 5604 | int totalHeight = 0; |
335 | ||
336 | // Build up an array list of the heights of each label/component pair. | |
337 | // Heights of zero indicate a proportional height. | |
338 | 5604 | Component previousComp = null; |
339 | 27816 | for (int i = 0; i < componentCount; ++i) { |
340 | 22212 | final Component childComp = (Component) components.get(i); |
341 | final int childHeight; | |
342 | 22212 | if (childComp instanceof JLabel) { |
343 | 22212 | final JLabel jlabel = (JLabel) childComp; |
344 | 22212 | final Component labelledComp = jlabel.getLabelFor(); |
345 | ||
346 | 22212 | labelWidth = Math.max(labelWidth, getPreferredWidth(jlabel)); |
347 | ||
348 | 22212 | if (labelledComp != null) { |
349 | 22212 | ++i; |
350 | 22212 | childHeight = getChildHeight(labelledComp); |
351 | 22212 | if (childHeight == 0) { |
352 | 7689 | ++unknownHeightCount; |
353 | } | |
354 | } else { | |
355 | 0 | childHeight = getPreferredHeight(jlabel); |
356 | } | |
357 | ||
358 | 22212 | totalHeight += childHeight + this.vgap; |
359 | 22212 | rowHeights.add(new Integer(childHeight)); |
360 | 22212 | } else { |
361 | // to manage the case there are no label/component | |
362 | // pairs but just one component | |
363 | 0 | childHeight = getChildHeight(childComp); |
364 | 0 | if (childHeight == 0) { |
365 | 0 | ++unknownHeightCount; |
366 | } | |
367 | ||
368 | 0 | totalHeight += childHeight + this.vgap; |
369 | 0 | rowHeights.add(new Integer(childHeight)); |
370 | } | |
371 | ||
372 | 22212 | previousComp = childComp; |
373 | } | |
374 | 5604 | totalHeight -= this.vgap; |
375 | ||
376 | 5604 | final Insets insets = parent.getInsets(); |
377 | 5604 | final int parentHeight = |
378 | parent.getHeight() - (insets.top + insets.bottom); | |
379 | // Set the child components to the heights in the array list | |
380 | // calculating the height of any proportional component on the | |
381 | // fly. FIXME - This assumes that the JLabel and the | |
382 | // component it labels have been added to the parent component | |
383 | // consecutively. | |
384 | 5604 | int y = insets.top; |
385 | 5604 | int row = 0; |
386 | 5604 | previousComp = null; |
387 | 27816 | for (int i = 0; i < componentCount; ++i) { |
388 | 22212 | Component childComp = (Component) components.get(i); |
389 | 22212 | if (childComp.isVisible()) { |
390 | int rowHeight; | |
391 | 22212 | int componentWidth = sectionWidth; |
392 | 22212 | int componentX = sectionX; |
393 | // If the component is a JLabel which has another | |
394 | // component assigned then position/size the label and | |
395 | // calculate the size of the registered component | |
396 | 22212 | if (childComp instanceof JLabel |
397 | && ((JLabel) childComp).getLabelFor() != null) { | |
398 | 22212 | i++; // Assumes the next child is the labelled component |
399 | 22212 | final JLabel jlabel = (JLabel) childComp; |
400 | 22212 | childComp = jlabel.getLabelFor(); |
401 | 22212 | jlabel.setBounds(sectionX, y, labelWidth, |
402 | getPreferredHeight(jlabel)); | |
403 | 22212 | componentWidth = sectionWidth - (labelWidth); |
404 | 22212 | componentX = sectionX + labelWidth; |
405 | } | |
406 | 22212 | rowHeight = rowHeights.get(row).intValue(); |
407 | 22212 | if (rowHeight == 0) { |
408 | try { | |
409 | 7689 | rowHeight = calculateHeight( |
410 | parentHeight, | |
411 | totalHeight, | |
412 | unknownHeightCount--, | |
413 | childComp); | |
414 | 0 | } catch (ArithmeticException e) { |
415 | 0 | String lookAndFeel = |
416 | UIManager.getLookAndFeel().getClass().getName(); | |
417 | 0 | throw new IllegalStateException( |
418 | "Division by zero laying out " | |
419 | + childComp.getClass().getName() | |
420 | + " on " + parent.getClass().getName() | |
421 | + " in section " + sectionNo | |
422 | + " using " | |
423 | + lookAndFeel, | |
424 | e); | |
425 | 7689 | } |
426 | 7689 | totalHeight += rowHeight; |
427 | } | |
428 | // Make sure the component width isn't any greater | |
429 | // than its maximum allowed width | |
430 | 22212 | if (childComp.getMaximumSize() != null |
431 | && getMaximumWidth(childComp) < componentWidth) { | |
432 | 1800 | componentWidth = getMaximumWidth(childComp); |
433 | } | |
434 | 22212 | childComp.setBounds(componentX, y, componentWidth, rowHeight); |
435 | 22212 | y += rowHeight + this.vgap; |
436 | 22212 | ++row; |
437 | 22212 | previousComp = childComp; |
438 | } | |
439 | } | |
440 | 5604 | } |
441 | ||
442 | /** | |
443 | * @param childComp a component | |
444 | * @return 0 for a resizable component or a positive value for its fixed | |
445 | * height | |
446 | */ | |
447 | private int getChildHeight(Component childComp) { | |
448 | 22212 | if (isResizable(childComp)) { |
449 | // If the child component is resizable then | |
450 | // we don't know it's actual size yet. | |
451 | // It will be calculated later as a | |
452 | // proportion of the available left over | |
453 | // space. For now this is flagged as zero. | |
454 | 7200 | return 0; |
455 | } else { | |
456 | // If a preferred height is not given or is | |
457 | // the same as the minimum height then fix the | |
458 | // height of this row. | |
459 | 15012 | return getMinimumHeight(childComp); |
460 | } | |
461 | } | |
462 | ||
463 | /** | |
464 | * A component is resizable if its minimum size is less than | |
465 | * its preferred size. | |
466 | * There is a workaround here for a bug introduced in JRE5 | |
467 | * where JComboBox minimum and preferred size now differ. | |
468 | * JComboBox is not resizable. | |
469 | * Anything in a JScrollPane is considered resizable | |
470 | * @param comp the component to check for resizability. | |
471 | * @return true if the given component should be resized to take u[p empty | |
472 | * space. | |
473 | */ | |
474 | private boolean isResizable(Component comp) { | |
475 | 22212 | if (comp == null) { |
476 | 0 | return false; |
477 | } | |
478 | 22212 | if (comp instanceof JComboBox) { |
479 | 0 | return false; |
480 | } | |
481 | 22212 | if (comp.getPreferredSize() == null) { |
482 | 0 | return false; |
483 | } | |
484 | 22212 | if (comp.getMinimumSize() == null) { |
485 | 0 | return false; |
486 | } | |
487 | 22212 | return (getMinimumHeight(comp) < getPreferredHeight(comp)); |
488 | } | |
489 | ||
490 | private final int calculateHeight( | |
491 | final int parentHeight, | |
492 | final int totalHeight, | |
493 | final int unknownHeightsLeft, | |
494 | final Component childComp) { | |
495 | 7689 | return Math.max( |
496 | (parentHeight - totalHeight) / unknownHeightsLeft, | |
497 | getMinimumHeight(childComp)); | |
498 | } | |
499 | ||
500 | private int getPreferredHeight(final Component comp) { | |
501 | 44424 | return (int) comp.getPreferredSize().getHeight(); |
502 | } | |
503 | ||
504 | private int getPreferredWidth(final Component comp) { | |
505 | 22212 | return (int) comp.getPreferredSize().getWidth(); |
506 | } | |
507 | ||
508 | private int getMinimumHeight(final Component comp) { | |
509 | 44913 | return (int) comp.getMinimumSize().getHeight(); |
510 | } | |
511 | ||
512 | private int getMaximumWidth(final Component comp) { | |
513 | 24012 | return (int) comp.getMaximumSize().getWidth(); |
514 | } | |
515 | ||
516 | /** | |
517 | * Create a new instance of the Separator that splits the layout in columns | |
518 | * @return the separator | |
519 | */ | |
520 | public static Seperator getSeparator() { | |
521 | 900 | return new Seperator(); |
522 | } | |
523 | ||
524 | /** | |
525 | * @return the horizontal gaps between components | |
526 | */ | |
527 | public int getHgap() { | |
528 | 0 | return this.hgap; |
529 | } | |
530 | ||
531 | /** | |
532 | * Set the horizontal gaps between components | |
533 | * @param hgap the horizontal gap | |
534 | */ | |
535 | public void setHgap(int hgap) { | |
536 | 4089 | this.hgap = hgap; |
537 | 4089 | } |
538 | ||
539 | /** | |
540 | * @return the vertical gaps between components | |
541 | */ | |
542 | public int getVgap() { | |
543 | 0 | return this.vgap; |
544 | } | |
545 | ||
546 | /** | |
547 | * Set the vertical gaps between components | |
548 | * @param vgap the horizontal gap | |
549 | */ | |
550 | public void setVgap(int vgap) { | |
551 | 0 | this.vgap = vgap; |
552 | 0 | } |
553 | } | |
554 | ||
555 | class Seperator extends JPanel { | |
556 | ||
557 | private static final long serialVersionUID = -4143634500959911688L; | |
558 | ||
559 | 900 | Seperator() { |
560 | 900 | super.setVisible(false); |
561 | 900 | } |
562 | } |