2 * Copyright (c) 2008-2012, Matthias Mann
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of Matthias Mann nor the names of its contributors may
15 * be used to endorse or promote products derived from this software
16 * without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 package de.matthiasmann.twl;
32 import de.matthiasmann.twl.renderer.AnimationState.StateKey;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
41 * A layout manager similar to Swing's GroupLayout
43 * This layout manager uses two independant layout groups:
44 * one for the horizontal axis
45 * one for the vertical axis.
46 * Every widget must be added to both the horizontal and the vertical group.
48 * When a widget is added to a group it will also be added as a child widget
49 * if it was not already added. You can add widgets to DialogLayout before
50 * adding them to a group to set the focus order.
52 * There are two kinds of groups:
53 * a sequential group which which behaves similar to BoxLayout
54 * a parallel group which alignes the start and size of each child
56 * Groups can be cascaded as a tree without restrictions.
58 * It is also possible to add widgets to DialogLayout without adding them
59 * to the layout groups. These widgets are then not touched by DialogLayout's
62 * When a widget is only added to either the horizontal or vertical groups
63 * and not both, then an IllegalStateException exception is created on layout.
65 * To help debugging the group construction you can set the system property
66 * "debugLayoutGroups" to "true" which will collect additional stack traces
67 * to help locate the source of the error.
69 * @author Matthias Mann
70 * @see #createParallelGroup()
71 * @see #createSequentialGroup()
73 public class DialogLayout extends Widget {
76 * Symbolic constant to refer to "small gap".
78 * @see Group#addGap(int)
79 * @see Group#addGap(int, int, int)
81 public static final int SMALL_GAP = -1;
84 * Symbolic constant to refer to "medium gap".
85 * @see #getMediumGap()
86 * @see Group#addGap(int)
87 * @see Group#addGap(int, int, int)
89 public static final int MEDIUM_GAP = -2;
92 * Symbolic constant to refer to "large gap".
94 * @see Group#addGap(int)
95 * @see Group#addGap(int, int, int)
97 public static final int LARGE_GAP = -3;
100 * Symbolic constant to refer to "default gap".
101 * The default gap is added (when enabled) between widgets.
103 * @see #getDefaultGap()
104 * @see #setAddDefaultGaps(boolean)
105 * @see #isAddDefaultGaps()
106 * @see Group#addGap(int)
107 * @see Group#addGap(int, int, int)
109 public static final int DEFAULT_GAP = -4;
111 private static final boolean DEBUG_LAYOUT_GROUPS = Widget.getSafeBooleanProperty("debugLayoutGroups");
113 protected Dimension smallGap;
114 protected Dimension mediumGap;
115 protected Dimension largeGap;
116 protected Dimension defaultGap;
117 protected ParameterMap namedGaps;
119 protected boolean addDefaultGaps = true;
120 protected boolean includeInvisibleWidgets = true;
121 protected boolean redoDefaultGaps;
122 protected boolean isPrepared;
123 protected boolean blockInvalidateLayoutTree;
124 protected boolean warnOnIncomplete;
130 * Debugging aid. Captures the stack trace where one of the group was last assigned.
132 Throwable debugStackTrace;
134 final HashMap<Widget, WidgetSpring> widgetSprings;
137 * Creates a new DialogLayout widget.
139 * Initially both the horizontal and the vertical group are null.
141 * @see #setHorizontalGroup(de.matthiasmann.twl.DialogLayout.Group)
142 * @see #setVerticalGroup(de.matthiasmann.twl.DialogLayout.Group)
144 public DialogLayout() {
145 widgetSprings = new HashMap<Widget, WidgetSpring>();
149 public Group getHorizontalGroup() {
154 * The horizontal group controls the position and size of all child
155 * widgets along the X axis.
157 * Every widget must be part of both horizontal and vertical group.
158 * Otherwise a IllegalStateException is thrown at layout time.
160 * If you want to change both horizontal and vertical group then
161 * it's recommended to set the other group first to null:
163 * setVerticalGroup(null);
164 * setHorizontalGroup(newHorzGroup);
165 * setVerticalGroup(newVertGroup);
168 * @param g the group used for the X axis
169 * @see #setVerticalGroup(de.matthiasmann.twl.DialogLayout.Group)
171 public void setHorizontalGroup(Group g) {
177 layoutGroupsChanged();
180 public Group getVerticalGroup() {
185 * The vertical group controls the position and size of all child
186 * widgets along the Y axis.
188 * Every widget must be part of both horizontal and vertical group.
189 * Otherwise a IllegalStateException is thrown at layout time.
191 * @param g the group used for the Y axis
192 * @see #setHorizontalGroup(de.matthiasmann.twl.DialogLayout.Group)
194 public void setVerticalGroup(Group g) {
200 layoutGroupsChanged();
203 public Dimension getSmallGap() {
207 public void setSmallGap(Dimension smallGap) {
208 this.smallGap = smallGap;
209 maybeInvalidateLayoutTree();
212 public Dimension getMediumGap() {
216 public void setMediumGap(Dimension mediumGap) {
217 this.mediumGap = mediumGap;
218 maybeInvalidateLayoutTree();
221 public Dimension getLargeGap() {
225 public void setLargeGap(Dimension largeGap) {
226 this.largeGap = largeGap;
227 maybeInvalidateLayoutTree();
230 public Dimension getDefaultGap() {
234 public void setDefaultGap(Dimension defaultGap) {
235 this.defaultGap = defaultGap;
236 maybeInvalidateLayoutTree();
239 public boolean isAddDefaultGaps() {
240 return addDefaultGaps;
244 * Determine whether default gaps should be added from the theme or not.
246 * @param addDefaultGaps if true then default gaps are added.
248 public void setAddDefaultGaps(boolean addDefaultGaps) {
249 this.addDefaultGaps = addDefaultGaps;
253 * removes all default gaps from all groups.
255 public void removeDefaultGaps() {
256 if(horz != null && vert != null) {
257 horz.removeDefaultGaps();
258 vert.removeDefaultGaps();
259 maybeInvalidateLayoutTree();
264 * Adds theme dependant default gaps to all groups.
266 public void addDefaultGaps() {
267 if(horz != null && vert != null) {
268 horz.addDefaultGap();
269 vert.addDefaultGap();
270 maybeInvalidateLayoutTree();
274 public boolean isIncludeInvisibleWidgets() {
275 return includeInvisibleWidgets;
279 * Controls whether invisible widgets should be included in the layout or
280 * not. If they are not included then the layout is recomputed when the
281 * visibility of a child widget changes.
283 * The default is true
285 * @param includeInvisibleWidgets If true then invisible widgets are included,
286 * if false they don't contribute to the layout.
288 public void setIncludeInvisibleWidgets(boolean includeInvisibleWidgets) {
289 if(this.includeInvisibleWidgets != includeInvisibleWidgets) {
290 this.includeInvisibleWidgets = includeInvisibleWidgets;
291 layoutGroupsChanged();
295 private void collectDebugStack() {
296 warnOnIncomplete = true;
297 if(DEBUG_LAYOUT_GROUPS) {
298 debugStackTrace = new Throwable("DialogLayout created/used here").fillInStackTrace();
302 private void warnOnIncomplete() {
303 warnOnIncomplete = false;
304 getLogger().log(Level.WARNING, "Dialog layout has incomplete state", debugStackTrace);
307 static Logger getLogger() {
308 return Logger.getLogger(DialogLayout.class.getName());
311 protected void applyThemeDialogLayout(ThemeInfo themeInfo) {
313 blockInvalidateLayoutTree = true;
314 setSmallGap(themeInfo.getParameterValue("smallGap", true, Dimension.class, Dimension.ZERO));
315 setMediumGap(themeInfo.getParameterValue("mediumGap", true, Dimension.class, Dimension.ZERO));
316 setLargeGap(themeInfo.getParameterValue("largeGap", true, Dimension.class, Dimension.ZERO));
317 setDefaultGap(themeInfo.getParameterValue("defaultGap", true, Dimension.class, Dimension.ZERO));
318 namedGaps = themeInfo.getParameterMap("namedGaps");
320 blockInvalidateLayoutTree = false;
326 protected void applyTheme(ThemeInfo themeInfo) {
327 super.applyTheme(themeInfo);
328 applyThemeDialogLayout(themeInfo);
332 public int getMinWidth() {
335 return horz.getMinSize(AXIS_X) + getBorderHorizontal();
337 return super.getMinWidth();
341 public int getMinHeight() {
344 return vert.getMinSize(AXIS_Y) + getBorderVertical();
346 return super.getMinHeight();
350 public int getPreferredInnerWidth() {
353 return horz.getPrefSize(AXIS_X);
355 return super.getPreferredInnerWidth();
359 public int getPreferredInnerHeight() {
362 return vert.getPrefSize(AXIS_Y);
364 return super.getPreferredInnerHeight();
368 public void adjustSize() {
369 if(horz != null && vert != null) {
371 int minWidth = horz.getMinSize(AXIS_X);
372 int minHeight = vert.getMinSize(AXIS_Y);
373 int prefWidth = horz.getPrefSize(AXIS_X);
374 int prefHeight = vert.getPrefSize(AXIS_Y);
375 int maxWidth = getMaxWidth();
376 int maxHeight = getMaxHeight();
378 computeSize(minWidth, prefWidth, maxWidth),
379 computeSize(minHeight, prefHeight, maxHeight));
385 protected void layout() {
386 if(horz != null && vert != null) {
389 } else if(warnOnIncomplete) {
394 protected void prepare() {
395 if(redoDefaultGaps) {
398 blockInvalidateLayoutTree = true;
402 blockInvalidateLayoutTree = false;
405 redoDefaultGaps = false;
409 for(WidgetSpring s : widgetSprings.values()) {
410 if(includeInvisibleWidgets || s.w.isVisible()) {
418 protected void doLayout() {
419 horz.setSize(AXIS_X, getInnerX(), getInnerWidth());
420 vert.setSize(AXIS_Y, getInnerY(), getInnerHeight());
422 for(WidgetSpring s : widgetSprings.values()) {
423 if(includeInvisibleWidgets || s.w.isVisible()) {
427 }catch(IllegalStateException ex) {
428 if(debugStackTrace != null && ex.getCause() == null) {
429 ex.initCause(debugStackTrace);
436 public void invalidateLayout() {
438 super.invalidateLayout();
442 protected void paintWidget(GUI gui) {
444 // super.paintWidget() is empty
448 protected void sizeChanged() {
454 protected void afterAddToGUI(GUI gui) {
456 super.afterAddToGUI(gui);
460 * Creates a new parallel group.
461 * All children in a parallel group share the same position and size of it's axis.
463 * @return the new parallel Group.
465 public Group createParallelGroup() {
466 return new ParallelGroup();
470 * Creates a parallel group and adds the specified widgets.
472 * @see #createParallelGroup()
473 * @param widgets the widgets to add
474 * @return a new parallel Group.
476 public Group createParallelGroup(Widget ... widgets) {
477 return createParallelGroup().addWidgets(widgets);
481 * Creates a parallel group and adds the specified groups.
483 * @see #createParallelGroup()
484 * @param groups the groups to add
485 * @return a new parallel Group.
487 public Group createParallelGroup(Group ... groups) {
488 return createParallelGroup().addGroups(groups);
492 * Creates a new sequential group.
493 * All children in a sequential group are ordered with increasing coordinates
494 * along it's axis in the order they are added to the group. The available
495 * size is distributed among the children depending on their min/preferred/max
498 * @return a new sequential Group.
500 public Group createSequentialGroup() {
501 return new SequentialGroup();
505 * Creates a sequential group and adds the specified widgets.
507 * @see #createSequentialGroup()
508 * @param widgets the widgets to add
509 * @return a new sequential Group.
511 public Group createSequentialGroup(Widget ... widgets) {
512 return createSequentialGroup().addWidgets(widgets);
516 * Creates a sequential group and adds the specified groups.
518 * @see #createSequentialGroup()
519 * @param groups the groups to add
520 * @return a new sequential Group.
522 public Group createSequentialGroup(Group ... groups) {
523 return createSequentialGroup().addGroups(groups);
527 public void insertChild(Widget child, int index) throws IndexOutOfBoundsException {
528 super.insertChild(child, index);
529 widgetSprings.put(child, new WidgetSpring(child));
533 public void removeAllChildren() {
534 super.removeAllChildren();
535 widgetSprings.clear();
537 layoutGroupsChanged();
541 public Widget removeChild(int index) throws IndexOutOfBoundsException {
542 final Widget widget = super.removeChild(index);
543 widgetSprings.remove(widget);
545 layoutGroupsChanged();
550 * Sets the alignment of the specified widget.
551 * The widget must have already been added to this container for this method to work.
553 * <p>The default alignment of a widget is {@link Alignment#FILL}</p>
555 * @param widget the widget for which the alignment should be set
556 * @param alignment the new alignment
557 * @return true if the widget's alignment was changed, false otherwise
559 public boolean setWidgetAlignment(Widget widget, Alignment alignment) {
561 throw new NullPointerException("widget");
563 if(alignment == null) {
564 throw new NullPointerException("alignment");
566 WidgetSpring ws = widgetSprings.get(widget);
568 assert widget.getParent() == this;
569 ws.alignment = alignment;
575 protected void recheckWidgets() {
577 horz.recheckWidgets();
580 vert.recheckWidgets();
584 protected void layoutGroupsChanged() {
585 redoDefaultGaps = true;
586 maybeInvalidateLayoutTree();
589 protected void maybeInvalidateLayoutTree() {
590 if(horz != null && vert != null && !blockInvalidateLayoutTree) {
596 protected void childVisibilityChanged(Widget child) {
597 if(!includeInvisibleWidgets) {
598 layoutGroupsChanged(); // this will also clear isPrepared
602 void removeChild(WidgetSpring widgetSpring) {
603 Widget widget = widgetSpring.w;
604 int idx = getChildIndex(widget);
606 super.removeChild(idx);
607 widgetSprings.remove(widget);
610 public static class Gap {
611 public final int min;
612 public final int preferred;
613 public final int max;
618 public Gap(int size) {
619 this(size, size, size);
621 public Gap(int min, int preferred) {
622 this(min, preferred, 32767);
624 public Gap(int min, int preferred, int max) {
626 throw new IllegalArgumentException("min");
628 if(preferred < min) {
629 throw new IllegalArgumentException("preferred");
631 if(max < 0 || (max > 0 && max < preferred)) {
632 throw new IllegalArgumentException("max");
635 this.preferred = preferred;
640 static final int AXIS_X = 0;
641 static final int AXIS_Y = 1;
643 static abstract class Spring {
644 abstract int getMinSize(int axis);
645 abstract int getPrefSize(int axis);
646 abstract int getMaxSize(int axis);
647 abstract void setSize(int axis, int pos, int size);
652 void collectAllSprings(HashSet<Spring> result) {
656 boolean isVisible() {
661 private static class WidgetSpring extends Spring {
676 WidgetSpring(Widget w) {
678 this.alignment = Alignment.FILL;
684 this.width = w.getWidth();
685 this.height = w.getHeight();
686 this.minWidth = w.getMinWidth();
687 this.minHeight = w.getMinHeight();
688 this.maxWidth = w.getMaxWidth();
689 this.maxHeight = w.getMaxHeight();
690 this.prefWidth = computeSize(minWidth, w.getPreferredWidth(), maxWidth);
691 this.prefHeight = computeSize(minHeight, w.getPreferredHeight(), maxHeight);
696 int getMinSize(int axis) {
698 case AXIS_X: return minWidth;
699 case AXIS_Y: return minHeight;
700 default: throw new IllegalArgumentException("axis");
705 int getPrefSize(int axis) {
707 case AXIS_X: return prefWidth;
708 case AXIS_Y: return prefHeight;
709 default: throw new IllegalArgumentException("axis");
714 int getMaxSize(int axis) {
716 case AXIS_X: return maxWidth;
717 case AXIS_Y: return maxHeight;
718 default: throw new IllegalArgumentException("axis");
723 void setSize(int axis, int pos, int size) {
724 this.flags |= 1 << axis;
735 throw new IllegalArgumentException("axis");
743 if(alignment != Alignment.FILL) {
744 int newWidth = Math.min(width, prefWidth);
745 int newHeight = Math.min(height, prefHeight);
747 x + alignment.computePositionX(width, newWidth),
748 y + alignment.computePositionY(height, newHeight));
749 w.setSize(newWidth, newHeight);
752 w.setSize(width, height);
757 boolean isVisible() {
758 return w.isVisible();
761 @SuppressWarnings("PointlessBitwiseExpression")
762 void invalidState() {
763 StringBuilder sb = new StringBuilder();
764 sb.append("Widget ").append(w)
765 .append(" with theme ").append(w.getTheme())
766 .append(" is not part of the following groups:");
767 if((flags & (1 << AXIS_X)) == 0) {
768 sb.append(" horizontal");
770 if((flags & (1 << AXIS_Y)) == 0) {
771 sb.append(" vertical");
773 throw new IllegalStateException(sb.toString());
777 private class GapSpring extends Spring {
781 final boolean isDefault;
783 GapSpring(int min, int pref, int max, boolean isDefault) {
784 convertConstant(AXIS_X, min);
785 convertConstant(AXIS_X, pref);
786 convertConstant(AXIS_X, max);
790 this.isDefault = isDefault;
794 int getMinSize(int axis) {
795 return convertConstant(axis, min);
799 int getPrefSize(int axis) {
800 return convertConstant(axis, pref);
804 int getMaxSize(int axis) {
805 return convertConstant(axis, max);
809 void setSize(int axis, int pos, int size) {
812 private int convertConstant(int axis, int value) {
831 throw new IllegalArgumentException("Invalid gap size: " + value);
835 } else if(axis == AXIS_X) {
843 static final Gap NO_GAP = new Gap(0,0,32767);
845 private class NamedGapSpring extends Spring {
848 public NamedGapSpring(String name) {
853 int getMaxSize(int axis) {
858 int getMinSize(int axis) {
863 int getPrefSize(int axis) {
864 return getGap().preferred;
868 void setSize(int axis, int pos, int size) {
871 private Gap getGap() {
872 if(namedGaps != null) {
873 return namedGaps.getParameterValue(name, true, Gap.class, NO_GAP);
879 public abstract class Group extends Spring {
880 final ArrayList<Spring> springs = new ArrayList<Spring>();
881 boolean alreadyAdded;
883 void checkGroup(DialogLayout owner) {
884 if(DialogLayout.this != owner) {
885 throw new IllegalArgumentException("Can't add group from different layout");
888 throw new IllegalArgumentException("Group already added to another group");
893 * Adds another group. A group can only be added once.
895 * WARNING: No check is made to prevent cycles.
897 * @param g the child Group
900 public Group addGroup(Group g) {
901 g.checkGroup(DialogLayout.this);
902 g.alreadyAdded = true;
908 * Adds several groups. A group can only be added once.
910 * WARNING: No check is made to prevent cycles.
912 * @param groups the groups to add
915 public Group addGroups(Group ... groups) {
916 for(Group g : groups) {
923 * Adds a widget to this group.
925 * <p>If the widget is already a child widget of the DialogLayout then it
926 * keeps it current settings, otherwise it is added the alignment is set
927 * to {@link Alignment#FILL}.</p>
929 * @param w the child widget.
931 * @see Widget#add(de.matthiasmann.twl.Widget)
933 public Group addWidget(Widget w) {
934 if(w.getParent() != DialogLayout.this) {
935 DialogLayout.this.add(w);
937 WidgetSpring s = widgetSprings.get(w);
939 throw new IllegalStateException("WidgetSpring for Widget not found: " + w);
946 * Adds a widget to this group.
948 * <p>If the widget is already a child widget of the DialogLayout then it
949 * it's alignment is set to the specified value overwriting any current
950 * alignment setting, otherwise it is added to the DialogLayout.</p>
952 * @param w the child widget.
953 * @param alignment the alignment of the child widget.
955 * @see Widget#add(de.matthiasmann.twl.Widget)
956 * @see #setWidgetAlignment(de.matthiasmann.twl.Widget, de.matthiasmann.twl.Alignment)
958 public Group addWidget(Widget w, Alignment alignment) {
960 setWidgetAlignment(w, alignment);
965 * Adds several widgets to this group. The widget is automatically added as child widget.
967 * @param widgets The widgets which should be added.
970 public Group addWidgets(Widget ... widgets) {
971 for(Widget w : widgets) {
978 * Adds several widgets to this group, inserting the specified gap in between.
979 * Each widget also gets an animation state set depending on it's position.
981 * The state gapName+"NotFirst" is set to false for widgets[0] and true for all others
982 * The state gapName+"NotLast" is set to false for widgets[n-1] and true for all others
984 * @param gapName the name of the gap to insert between widgets
985 * @param widgets The widgets which should be added.
988 public Group addWidgetsWithGap(String gapName, Widget ... widgets) {
989 StateKey stateNotFirst = StateKey.get(gapName.concat("NotFirst"));
990 StateKey stateNotLast = StateKey.get(gapName.concat("NotLast"));
991 for(int i=0,n=widgets.length ; i<n ;i++) {
995 Widget w = widgets[i];
997 AnimationState as = w.getAnimationState();
998 as.setAnimationState(stateNotFirst, i > 0);
999 as.setAnimationState(stateNotLast, i < n-1);
1005 * Adds a generic gap. Can use symbolic gap names.
1007 * @param min the minimum size in pixels or a symbolic constant
1008 * @param pref the preferred size in pixels or a symbolic constant
1009 * @param max the maximum size in pixels or a symbolic constant
1010 * @return this Group
1011 * @see DialogLayout#SMALL_GAP
1012 * @see DialogLayout#MEDIUM_GAP
1013 * @see DialogLayout#LARGE_GAP
1014 * @see DialogLayout#DEFAULT_GAP
1016 public Group addGap(int min, int pref, int max) {
1017 addSpring(new GapSpring(min, pref, max, false));
1022 * Adds a fixed sized gap. Can use symbolic gap names.
1024 * @param size the size in pixels or a symbolic constant
1025 * @return this Group
1026 * @see DialogLayout#SMALL_GAP
1027 * @see DialogLayout#MEDIUM_GAP
1028 * @see DialogLayout#LARGE_GAP
1029 * @see DialogLayout#DEFAULT_GAP
1031 public Group addGap(int size) {
1032 addSpring(new GapSpring(size, size, size, false));
1037 * Adds a gap with minimum size. Can use symbolic gap names.
1039 * @param minSize the minimum size in pixels or a symbolic constant
1040 * @return this Group
1041 * @see DialogLayout#SMALL_GAP
1042 * @see DialogLayout#MEDIUM_GAP
1043 * @see DialogLayout#LARGE_GAP
1044 * @see DialogLayout#DEFAULT_GAP
1046 public Group addMinGap(int minSize) {
1047 addSpring(new GapSpring(minSize, minSize, Short.MAX_VALUE, false));
1052 * Adds a flexible gap with no minimum size.
1054 * <p>This is equivalent to {@code addGap(0, 0, Short.MAX_VALUE) }</p>
1055 * @return this Group
1057 public Group addGap() {
1058 addSpring(new GapSpring(0, 0, Short.MAX_VALUE, false));
1065 * <p>Named gaps are configured via the theme parameter "namedGaps" which
1066 * maps from names to <gap> objects.</p>
1068 * <p>They behave equal to {@link #addGap(int, int, int) }.</p>
1070 * @param name the name of the gap (vcase sensitive)
1071 * @return this Group
1073 public Group addGap(String name) {
1074 if(name.length() == 0) {
1075 throw new IllegalArgumentException("name");
1077 addSpring(new NamedGapSpring(name));
1082 * Remove all default gaps from this and child groups
1084 public void removeDefaultGaps() {
1085 for(int i=springs.size() ; i-->0 ;) {
1086 Spring s = springs.get(i);
1087 if(s instanceof GapSpring) {
1088 if(((GapSpring)s).isDefault) {
1091 } else if(s instanceof Group) {
1092 ((Group)s).removeDefaultGaps();
1098 * Add a default gap between all children except if the neighbour is already a Gap.
1100 public void addDefaultGap() {
1101 for(int i=0 ; i<springs.size() ; i++) {
1102 Spring s = springs.get(i);
1103 if(s instanceof Group) {
1104 ((Group)s).addDefaultGap();
1110 * Removes the specified group from this group.
1112 * @param g the group to remove
1113 * @param removeWidgets if true all widgets in the specified group
1114 * should be removed from the {@code DialogLayout}
1115 * @return true if it was found and removed, false otherwise
1117 public boolean removeGroup(Group g, boolean removeWidgets) {
1118 for(int i=0 ; i<springs.size() ; i++) {
1119 if(springs.get(i) == g) {
1123 DialogLayout.this.recheckWidgets();
1125 DialogLayout.this.layoutGroupsChanged();
1133 * Removes all elements from this group
1135 * @param removeWidgets if true all widgets in this group are removed
1136 * from the {@code DialogLayout}
1138 public void clear(boolean removeWidgets) {
1144 DialogLayout.this.recheckWidgets();
1146 DialogLayout.this.layoutGroupsChanged();
1149 void addSpring(Spring s) {
1151 DialogLayout.this.layoutGroupsChanged();
1154 void recheckWidgets() {
1155 for(int i=springs.size() ; i-->0 ;) {
1156 Spring s = springs.get(i);
1157 if(s instanceof WidgetSpring) {
1158 if(!widgetSprings.containsKey(((WidgetSpring)s).w)) {
1161 } else if(s instanceof Group) {
1162 ((Group)s).recheckWidgets();
1167 void removeWidgets() {
1168 for(int i=springs.size() ; i-->0 ;) {
1169 Spring s = springs.get(i);
1170 if(s instanceof WidgetSpring) {
1171 removeChild((WidgetSpring)s);
1172 } else if(s instanceof Group) {
1173 ((Group)s).removeWidgets();
1179 static class SpringDelta implements Comparable<SpringDelta> {
1183 SpringDelta(int idx, int delta) {
1188 public int compareTo(SpringDelta o) {
1189 return delta - o.delta;
1193 class SequentialGroup extends Group {
1198 int getMinSize(int axis) {
1200 for(int i=0,n=springs.size() ; i<n ; i++) {
1201 Spring s = springs.get(i);
1202 if(includeInvisibleWidgets || s.isVisible()) {
1203 size += s.getMinSize(axis);
1210 int getPrefSize(int axis) {
1212 for(int i=0,n=springs.size() ; i<n ; i++) {
1213 Spring s = springs.get(i);
1214 if(includeInvisibleWidgets || s.isVisible()) {
1215 size += s.getPrefSize(axis);
1222 int getMaxSize(int axis) {
1224 boolean hasMax = false;
1225 for(int i=0,n=springs.size() ; i<n ; i++) {
1226 Spring s = springs.get(i);
1227 if(includeInvisibleWidgets || s.isVisible()) {
1228 int max = s.getMaxSize(axis);
1233 size += s.getPrefSize(axis);
1237 return hasMax ? size : 0;
1241 * Add a default gap between all children except if the neighbour is already a Gap.
1244 public void addDefaultGap() {
1245 if(springs.size() > 1) {
1246 boolean wasGap = true;
1247 for(int i=0 ; i<springs.size() ; i++) {
1248 Spring s = springs.get(i);
1249 if(includeInvisibleWidgets || s.isVisible()) {
1250 boolean isGap = (s instanceof GapSpring) || (s instanceof NamedGapSpring);
1251 if(!isGap && !wasGap) {
1252 springs.add(i++, new GapSpring(DEFAULT_GAP, DEFAULT_GAP, DEFAULT_GAP, true));
1258 super.addDefaultGap();
1262 void setSize(int axis, int pos, int size) {
1263 int prefSize = getPrefSize(axis);
1264 if(size == prefSize) {
1265 for(Spring s : springs) {
1266 if(includeInvisibleWidgets || s.isVisible()) {
1267 int spref = s.getPrefSize(axis);
1268 s.setSize(axis, pos, spref);
1272 } else if(springs.size() == 1) {
1273 // no need to check visibility flag
1274 Spring s = springs.get(0);
1275 s.setSize(axis, pos, size);
1276 } else if(springs.size() > 1) {
1277 setSizeNonPref(axis, pos, size, prefSize);
1281 private void setSizeNonPref(int axis, int pos, int size, int prefSize) {
1282 int delta = size - prefSize;
1283 boolean useMin = delta < 0;
1288 SpringDelta[] deltas = new SpringDelta[springs.size()];
1290 for(int i=0 ; i<springs.size() ; i++) {
1291 Spring s = springs.get(i);
1292 if(includeInvisibleWidgets || s.isVisible()) {
1294 ? s.getPrefSize(axis) - s.getMinSize(axis)
1295 : s.getMaxSize(axis) - s.getPrefSize(axis);
1297 deltas[resizeable++] = new SpringDelta(i, sdelta);
1301 if(resizeable > 0) {
1302 if(resizeable > 1) {
1303 Arrays.sort(deltas, 0, resizeable);
1306 int sizes[] = new int[springs.size()];
1308 int remaining = resizeable;
1309 for(int i=0 ; i<resizeable ; i++) {
1310 SpringDelta d = deltas[i];
1312 int sdelta = delta / remaining;
1313 int ddelta = Math.min(d.delta, sdelta);
1320 sizes[d.idx] = ddelta;
1323 for(int i=0 ; i<springs.size() ; i++) {
1324 Spring s = springs.get(i);
1325 if(includeInvisibleWidgets || s.isVisible()) {
1326 int ssize = s.getPrefSize(axis) + sizes[i];
1327 s.setSize(axis, pos, ssize);
1332 for(Spring s : springs) {
1333 if(includeInvisibleWidgets || s.isVisible()) {
1336 ssize = s.getMinSize(axis);
1338 ssize = s.getMaxSize(axis);
1340 ssize = s.getPrefSize(axis);
1343 s.setSize(axis, pos, ssize);
1351 class ParallelGroup extends Group {
1356 int getMinSize(int axis) {
1358 for(int i=0,n=springs.size() ; i<n ; i++) {
1359 Spring s = springs.get(i);
1360 if(includeInvisibleWidgets || s.isVisible()) {
1361 size = Math.max(size, s.getMinSize(axis));
1368 int getPrefSize(int axis) {
1370 for(int i=0,n=springs.size() ; i<n ; i++) {
1371 Spring s = springs.get(i);
1372 if(includeInvisibleWidgets || s.isVisible()) {
1373 size = Math.max(size, s.getPrefSize(axis));
1380 int getMaxSize(int axis) {
1382 for(int i=0,n=springs.size() ; i<n ; i++) {
1383 Spring s = springs.get(i);
1384 if(includeInvisibleWidgets || s.isVisible()) {
1385 size = Math.max(size, s.getMaxSize(axis));
1392 void setSize(int axis, int pos, int size) {
1393 for(int i=0,n=springs.size() ; i<n ; i++) {
1394 Spring s = springs.get(i);
1395 if(includeInvisibleWidgets || s.isVisible()) {
1396 s.setSize(axis, pos, size);
1402 public Group addGap() {
1403 getLogger().log(Level.WARNING, "Useless call to addGap() on ParallelGroup", new Throwable());