JAL-3032 adds Java 8 functionality (1/2)
[jalview.git] / src / net / miginfocom / swing / MigLayout.java
1 package net.miginfocom.swing;
2 /*
3  * License (BSD):
4  * ==============
5  *
6  * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without modification,
10  * are permitted provided that the following conditions are met:
11  * Redistributions of source code must retain the above copyright notice, this list
12  * of conditions and the following disclaimer.
13  * Redistributions in binary form must reproduce the above copyright notice, this
14  * list of conditions and the following disclaimer in the documentation and/or other
15  * materials provided with the distribution.
16  * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
17  * used to endorse or promote products derived from this software without specific
18  * prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
26  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
29  * OF SUCH DAMAGE.
30  *
31  * @version 1.0
32  * @author Mikael Grev, MiG InfoCom AB
33  *         Date: 2006-sep-08
34  */
35
36 import java.awt.Component;
37 import java.awt.Container;
38 import java.awt.Dimension;
39 import java.awt.Insets;
40 import java.awt.LayoutManager;
41 import java.awt.LayoutManager2;
42 import java.awt.Point;
43 import java.awt.Window;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.IdentityHashMap;
51 import java.util.Iterator;
52 import java.util.Map;
53
54 import javax.swing.BoxLayout;
55 import javax.swing.JComponent;
56 import javax.swing.JEditorPane;
57 import javax.swing.JPopupMenu;
58 import javax.swing.JTextArea;
59 import javax.swing.OverlayLayout;
60 import javax.swing.SwingUtilities;
61 import javax.swing.Timer;
62
63 import net.miginfocom.layout.AC;
64 import net.miginfocom.layout.BoundSize;
65 import net.miginfocom.layout.CC;
66 import net.miginfocom.layout.ComponentWrapper;
67 import net.miginfocom.layout.ConstraintParser;
68 import net.miginfocom.layout.ContainerWrapper;
69 import net.miginfocom.layout.Grid;
70 import net.miginfocom.layout.LC;
71 import net.miginfocom.layout.LayoutCallback;
72 import net.miginfocom.layout.LayoutUtil;
73 import net.miginfocom.layout.PlatformDefaults;
74 import net.miginfocom.layout.UnitValue;
75
76 /** A very flexible layout manager.
77  * <p>
78  * Read the documentation that came with this layout manager for information on usage.
79  */
80 public class MigLayout implements LayoutManager2//, Externalizable
81 {
82         // ******** Instance part ********
83
84         /** The component to string constraints mappings.
85          */
86         private final Map<Component, Object> scrConstrMap = new IdentityHashMap<Component, Object>(8);
87
88         /** Hold the serializable text representation of the constraints.
89          */
90         private Object layoutConstraints = "", colConstraints = "", rowConstraints = "";    // Should never be null!
91
92         // ******** Transient part ********
93
94         private transient ContainerWrapper cacheParentW = null;
95
96         private transient final Map<ComponentWrapper, CC> ccMap = new HashMap<ComponentWrapper, CC>(8);
97         private transient javax.swing.Timer debugTimer = null;
98
99         private transient LC lc = null;
100         private transient AC colSpecs = null, rowSpecs = null;
101         private transient Grid grid = null;
102         private transient int lastModCount = PlatformDefaults.getModCount();
103         private transient int lastHash = -1;
104         private transient Dimension lastInvalidSize = null;
105         private transient boolean lastWasInvalid = false;  // Added in 3.7.1. May have regressions
106         private transient Dimension lastParentSize = null;
107
108         private transient ArrayList<LayoutCallback> callbackList = null;
109
110         private transient boolean dirty = true;
111
112         /** Constructor with no constraints.
113          */
114         public MigLayout()
115         {
116                 this("", "", "");
117         }
118
119         /** Constructor.
120          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as "".
121          */
122         public MigLayout(String layoutConstraints)
123         {
124                 this(layoutConstraints, "", "");
125         }
126
127         /** Constructor.
128          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as "".
129          * @param colConstraints The constraints for the columns in the grid. <code>null</code> will be treated as "".
130          */
131         public MigLayout(String layoutConstraints, String colConstraints)
132         {
133                 this(layoutConstraints, colConstraints, "");
134         }
135
136         /** Constructor.
137          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as "".
138          * @param colConstraints The constraints for the columns in the grid. <code>null</code> will be treated as "".
139          * @param rowConstraints The constraints for the rows in the grid. <code>null</code> will be treated as "".
140          */
141         public MigLayout(String layoutConstraints, String colConstraints, String rowConstraints)
142         {
143                 setLayoutConstraints(layoutConstraints);
144                 setColumnConstraints(colConstraints);
145                 setRowConstraints(rowConstraints);
146         }
147
148         /** Constructor.
149          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as an empty constraint.
150          */
151         public MigLayout(LC layoutConstraints)
152         {
153                 this(layoutConstraints, null, null);
154         }
155
156         /** Constructor.
157          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as an empty constraint.
158          * @param colConstraints The constraints for the columns in the grid. <code>null</code> will be treated as an empty constraint.
159          */
160         public MigLayout(LC layoutConstraints, AC colConstraints)
161         {
162                 this(layoutConstraints, colConstraints, null);
163         }
164
165         /** Constructor.
166          * @param layoutConstraints The constraints that concern the whole layout. <code>null</code> will be treated as an empty constraint.
167          * @param colConstraints The constraints for the columns in the grid. <code>null</code> will be treated as an empty constraint.
168          * @param rowConstraints The constraints for the rows in the grid. <code>null</code> will be treated as an empty constraint.
169          */
170         public MigLayout(LC layoutConstraints, AC colConstraints, AC rowConstraints)
171         {
172                 setLayoutConstraints(layoutConstraints);
173                 setColumnConstraints(colConstraints);
174                 setRowConstraints(rowConstraints);
175         }
176
177         /** Returns layout constraints either as a <code>String</code> or {@link net.miginfocom.layout.LC} depending what was sent in
178          * to the constructor or set with {@link #setLayoutConstraints(Object)}.
179          * @return The layout constraints either as a <code>String</code> or {@link net.miginfocom.layout.LC} depending what was sent in
180          * to the constructor or set with {@link #setLayoutConstraints(Object)}. Never <code>null</code>.
181          */
182         public Object getLayoutConstraints()
183         {
184                 return layoutConstraints;
185         }
186
187         /** Sets the layout constraints for the layout manager instance as a String.
188          * <p>
189          * See the class JavaDocs for information on how this string is formatted.
190          * @param constr The layout constraints as a String or {@link net.miginfocom.layout.LC} representation. <code>null</code> is converted to <code>""</code> for storage.
191          * @throws RuntimeException if the constraint was not valid.
192          */
193         public void setLayoutConstraints(Object constr)
194         {
195                 if (constr == null || constr instanceof String) {
196                         constr = ConstraintParser.prepare((String) constr);
197                         lc = ConstraintParser.parseLayoutConstraint((String) constr);
198                 } else if (constr instanceof LC) {
199                         lc = (LC) constr;
200                 } else {
201                         throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString());
202                 }
203                 layoutConstraints = constr;
204                 dirty = true;
205         }
206
207         /** Returns the column layout constraints either as a <code>String</code> or {@link net.miginfocom.layout.AC}.
208          * @return The column constraints either as a <code>String</code> or {@link net.miginfocom.layout.AC} depending what was sent in
209          * to the constructor or set with {@link #setColumnConstraints(Object)}. Never <code>null</code>.
210          */
211         public Object getColumnConstraints()
212         {
213                 return colConstraints;
214         }
215
216         /** Sets the column layout constraints for the layout manager instance as a String.
217          * <p>
218          * See the class JavaDocs for information on how this string is formatted.
219          * @param constr The column layout constraints as a String or {@link net.miginfocom.layout.AC} representation. <code>null</code> is converted to <code>""</code> for storage.
220          * @throws RuntimeException if the constraint was not valid.
221          */
222         public void setColumnConstraints(Object constr)
223         {
224                 if (constr == null || constr instanceof String) {
225                         constr = ConstraintParser.prepare((String) constr);
226                         colSpecs = ConstraintParser.parseColumnConstraints((String) constr);
227                 } else if (constr instanceof AC) {
228                         colSpecs = (AC) constr;
229                 } else {
230                         throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString());
231                 }
232                 colConstraints = constr;
233                 dirty = true;
234         }
235
236         /** Returns the row layout constraints either as a <code>String</code> or {@link net.miginfocom.layout.AC}.
237          * @return The row constraints either as a <code>String</code> or {@link net.miginfocom.layout.AC} depending what was sent in
238          * to the constructor or set with {@link #setRowConstraints(Object)}. Never <code>null</code>.
239          */
240         public Object getRowConstraints()
241         {
242                 return rowConstraints;
243         }
244
245         /** Sets the row layout constraints for the layout manager instance as a String.
246          * <p>
247          * See the class JavaDocs for information on how this string is formatted.
248          * @param constr The row layout constraints as a String or {@link net.miginfocom.layout.AC} representation. <code>null</code> is converted to <code>""</code> for storage.
249          * @throws RuntimeException if the constraint was not valid.
250          */
251         public void setRowConstraints(Object constr)
252         {
253                 if (constr == null || constr instanceof String) {
254                         constr = ConstraintParser.prepare((String) constr);
255                         rowSpecs = ConstraintParser.parseRowConstraints((String) constr);
256                 } else if (constr instanceof AC) {
257                         rowSpecs = (AC) constr;
258                 } else {
259                         throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString());
260                 }
261                 rowConstraints = constr;
262                 dirty = true;
263         }
264
265         /** Returns a shallow copy of the constraints map.
266          * @return A  shallow copy of the constraints map. Never <code>null</code>.
267          */
268         public Map<Component, Object> getConstraintMap()
269         {
270                 return new IdentityHashMap<Component, Object>(scrConstrMap);
271         }
272
273         /** Sets the constraints map.
274          * @param map The map. Will be copied.
275          */
276         public void setConstraintMap(Map<Component, Object> map)
277         {
278                 scrConstrMap.clear();
279                 ccMap.clear();
280                 for (Map.Entry<Component, Object> e : map.entrySet())
281                         setComponentConstraintsImpl(e.getKey(), e.getValue(), true);
282         }
283
284         /** Returns the component constraints as a String representation. This string is the exact string as set with {@link #setComponentConstraints(java.awt.Component, Object)}
285          * or set when adding the component to the parent component.
286          * <p>
287          * See the class JavaDocs for information on how this string is formatted.
288          * @param comp The component to return the constraints for.
289          * @return The component constraints as a String representation or <code>null</code> if the component is not registered
290          * with this layout manager. The returned values is either a String or a {@link net.miginfocom.layout.CC}
291          * depending on what constraint was sent in when the component was added. May be <code>null</code>.
292          */
293         public Object getComponentConstraints(Component comp)
294         {
295                 synchronized(comp.getParent().getTreeLock()) {
296                         return scrConstrMap.get(comp);
297                 }
298         }
299
300         /** Sets the component constraint for the component that already must be handled by this layout manager.
301          * <p>
302          * See the class JavaDocs for information on how this string is formatted.
303          * @param constr The component constraints as a String or {@link net.miginfocom.layout.CC}. <code>null</code> is ok.
304          * @param comp The component to set the constraints for.
305          * @throws RuntimeException if the constraint was not valid.
306          * @throws IllegalArgumentException If the component is not handling the component.
307          */
308         public void setComponentConstraints(Component comp, Object constr)
309         {
310                 setComponentConstraintsImpl(comp, constr, false);
311         }
312
313         /** Sets the component constraint for the component that already must be handled by this layout manager.
314          * <p>
315          * See the class JavaDocs for information on how this string is formatted.
316          * @param constr The component constraints as a String or {@link net.miginfocom.layout.CC}. <code>null</code> is ok.
317          * @param comp The component to set the constraints for.
318          * @param noCheck Does not check if the component is handled if true
319          * @throws RuntimeException if the constraint was not valid.
320          * @throws IllegalArgumentException If the component is not handling the component.
321          */
322         private void setComponentConstraintsImpl(Component comp, Object constr, boolean noCheck)
323         {
324                 Container parent = comp.getParent();
325                 synchronized(parent != null ? parent.getTreeLock() : new Object()) { // 3.7.2. No sync if not added to a hierarchy. Defeats a NPE.
326                         if (noCheck == false && scrConstrMap.containsKey(comp) == false)
327                                 throw new IllegalArgumentException("Component must already be added to parent!");
328
329                         ComponentWrapper cw = new SwingComponentWrapper(comp);
330
331                         if (constr == null || constr instanceof String) {
332                                 String cStr = ConstraintParser.prepare((String) constr);
333
334                                 scrConstrMap.put(comp, constr);
335                                 ccMap.put(cw, ConstraintParser.parseComponentConstraint(cStr));
336
337                         } else if (constr instanceof CC) {
338
339                                 scrConstrMap.put(comp, constr);
340                                 ccMap.put(cw, (CC) constr);
341
342                         } else {
343                                 throw new IllegalArgumentException("Constraint must be String or ComponentConstraint: " + constr.getClass().toString());
344                         }
345
346                         dirty = true;
347                 }
348         }
349
350         /** Returns if this layout manager is currently managing this component.
351          * @param c The component to check. If <code>null</code> then <code>false</code> will be returned.
352          * @return If this layout manager is currently managing this component.
353          */
354         public boolean isManagingComponent(Component c)
355         {
356                 return scrConstrMap.containsKey(c);
357         }
358
359         /** Adds the callback function that will be called at different stages of the layout cycle.
360          * @param callback The callback. Not <code>null</code>.
361          */
362         public void addLayoutCallback(LayoutCallback callback)
363         {
364                 if (callback == null)
365                         throw new NullPointerException();
366
367                 if (callbackList == null)
368                         callbackList = new ArrayList<LayoutCallback>(1);
369
370                 callbackList.add(callback);
371
372                 grid = null;
373         }
374
375         /** Removes the callback if it exists.
376          * @param callback The callback. May be <code>null</code>.
377          */
378         public void removeLayoutCallback(LayoutCallback callback)
379         {
380                 if (callbackList != null)
381                         callbackList.remove(callback);
382         }
383
384         /** Sets the debugging state for this layout manager instance. If debug is turned on a timer will repaint the last laid out parent
385          * with debug information on top.
386          * <p>
387          * Red fill and dashed red outline is used to indicate occupied cells in the grid. Blue dashed outline indicate
388          * component bounds set.
389          * <p>
390          * Note that debug can also be set on the layout constraints. There it will be persisted. The value set here will not. See the class
391          * JavaDocs for information.
392          * @param parentW The parent to set debug for.
393          * @param b <code>true</code> means debug is turned on.
394          */
395         private void setDebug(final ComponentWrapper parentW, boolean b)
396         {
397                 if (b && (debugTimer == null || debugTimer.getDelay() != getDebugMillis())) {
398                         if (debugTimer != null)
399                                 debugTimer.stop();
400
401                         ContainerWrapper pCW = parentW.getParent();
402                         final Component parent = pCW != null ? (Component) pCW.getComponent() : null;
403
404                         debugTimer = new Timer(getDebugMillis(), new MyDebugRepaintListener());
405
406                         if (parent != null) {
407                                 SwingUtilities.invokeLater(new Runnable() {
408                                         @Override
409                                         public void run() {
410                                                 Container p = parent.getParent();
411                                                 if (p != null) {
412                                                         if (p instanceof JComponent) {
413                                                                 ((JComponent) p).revalidate();
414                                                         } else {
415                                                                 parent.invalidate();
416                                                                 p.validate();
417                                                         }
418                                                 }
419                                         }
420                                 });
421                         }
422
423                         debugTimer.setInitialDelay(100);
424                         debugTimer.start();
425
426                 } else if (!b && debugTimer != null) {
427                         debugTimer.stop();
428                         debugTimer = null;
429                 }
430         }
431
432         /** Returns the current debugging state.
433          * @return The current debugging state.
434          */
435         private boolean getDebug()
436         {
437                 return debugTimer != null;
438         }
439
440         /** Returns the debug millis. Combines the value from {@link net.miginfocom.layout.LC#getDebugMillis()} and {@link net.miginfocom.layout.LayoutUtil#getGlobalDebugMillis()}
441          * @return The combined value.
442          */
443         private int getDebugMillis()
444         {
445                 int globalDebugMillis = LayoutUtil.getGlobalDebugMillis();
446                 return globalDebugMillis > 0 ? globalDebugMillis : lc.getDebugMillis();
447         }
448
449         /** Check if something has changed and if so recreate it to the cached objects.
450          * @param parent The parent that is the target for this layout manager.
451          */
452         private void checkCache(Container parent)
453         {
454                 if (parent == null)
455                         return;
456
457                 if (dirty)
458                         grid = null;
459
460                 cleanConstraintMaps(parent);
461
462                 // Check if the grid is valid
463                 int mc = PlatformDefaults.getModCount();
464                 if (lastModCount != mc) {
465                         grid = null;
466                         lastModCount = mc;
467                 }
468
469                 if (!parent.isValid()) {
470                         if (!lastWasInvalid) {
471                                 lastWasInvalid = true;
472
473                                 int hash = 0;
474                                 boolean resetLastInvalidOnParent = false; // Added in 3.7.3 to resolve a timing regression introduced in 3.7.1
475                                 for (ComponentWrapper wrapper : ccMap.keySet()) {
476                                         Object component = wrapper.getComponent();
477                                         if (component instanceof JTextArea || component instanceof JEditorPane)
478                                                 resetLastInvalidOnParent = true;
479
480                                         hash ^= wrapper.getLayoutHashCode();
481                                         hash += 285134905;
482                                 }
483                                 if (resetLastInvalidOnParent)
484                                         resetLastInvalidOnParent(parent);
485
486                                 if (hash != lastHash) {
487                                         grid = null;
488                                         lastHash = hash;
489                                 }
490
491                                 Dimension ps = parent.getSize();
492                                 if (lastInvalidSize == null || !lastInvalidSize.equals(ps)) {
493                                         grid = null;
494                                         lastInvalidSize = ps;
495                                 }
496                         }
497                 } else {
498                         lastWasInvalid = false;
499                 }
500
501                 ContainerWrapper par = checkParent(parent);
502
503                 setDebug(par, getDebugMillis() > 0);
504
505                 if (grid == null)
506                         grid = new Grid(par, lc, rowSpecs, colSpecs, ccMap, callbackList);
507
508                 dirty = false;
509         }
510
511         /** Checks so all components in ccMap actually exist in the parent's collection. Removes
512          * any references that don't.
513          * @param parent The parent to compare ccMap against. Never null.
514          */
515         private void cleanConstraintMaps(Container parent)
516         {
517                 HashSet<Component> parentCompSet = new HashSet<Component>(Arrays.asList(parent.getComponents()));
518
519                 Iterator<Map.Entry<ComponentWrapper, CC>> it = ccMap.entrySet().iterator();
520                 while(it.hasNext()) {
521                         Component c = (Component) it.next().getKey().getComponent();
522                         if (parentCompSet.contains(c) == false) {
523                                 it.remove();
524                                 scrConstrMap.remove(c);
525                         }
526                 }
527         }
528
529         /**
530          * @since 3.7.3
531          */
532         private void resetLastInvalidOnParent(Container parent)
533         {
534                 while (parent != null) {
535                         LayoutManager layoutManager = parent.getLayout();
536                         if (layoutManager instanceof MigLayout) {
537                                 ((MigLayout) layoutManager).lastWasInvalid = false;
538                         }
539                         parent = parent.getParent();
540                 }
541         }
542
543         private ContainerWrapper checkParent(Container parent)
544         {
545                 if (parent == null)
546                         return null;
547
548                 if (cacheParentW == null || cacheParentW.getComponent() != parent)
549                         cacheParentW = new SwingContainerWrapper(parent);
550
551                 return cacheParentW;
552         }
553
554         private long lastSize = 0;
555
556         @Override
557         public void layoutContainer(final Container parent)
558         {
559                 synchronized(parent.getTreeLock()) {
560                         checkCache(parent);
561
562                         Insets i = parent.getInsets();
563                         int[] b = new int[] {
564                                         i.left,
565                                         i.top,
566                                         parent.getWidth() - i.left - i.right,
567                                         parent.getHeight() - i.top - i.bottom
568                         };
569
570                         if (grid.layout(b, lc.getAlignX(), lc.getAlignY(), getDebug())) {
571                                 grid = null;
572                                 checkCache(parent);
573                                 grid.layout(b, lc.getAlignX(), lc.getAlignY(), getDebug());
574                         }
575
576                         long newSize = grid.getHeight()[1] + (((long) grid.getWidth()[1]) << 32);
577                         if (lastSize != newSize) {
578                                 lastSize = newSize;
579                                 final ContainerWrapper containerWrapper = checkParent(parent);
580                                 Window win = ((Window) SwingUtilities.getAncestorOfClass(Window.class, (Component)containerWrapper.getComponent()));
581                                 if (win != null) {
582                                    if (win.isVisible()) {
583                                            SwingUtilities.invokeLater(new Runnable() {
584                                                    @Override
585                                                    public void run() {
586                                                            adjustWindowSize(containerWrapper);
587                                                    }
588                                            });
589                                    } else {
590                                            adjustWindowSize(containerWrapper);
591                                    }
592                                 }
593                         }
594                         lastInvalidSize = null;
595                 }
596         }
597
598         /** Checks the parent window/popup if its size is within parameters as set by the LC.
599          * @param parent The parent who's window to possibly adjust the size for.
600          */
601         private void adjustWindowSize(ContainerWrapper parent)
602         {
603                 BoundSize wBounds = lc.getPackWidth();
604                 BoundSize hBounds = lc.getPackHeight();
605
606                 if (wBounds == BoundSize.NULL_SIZE && hBounds == BoundSize.NULL_SIZE)
607                         return;
608
609                 Container packable = getPackable((Component) parent.getComponent());
610
611                 if (packable != null) {
612
613                         Component pc = (Component) parent.getComponent();
614
615                         Container c = pc instanceof Container ? (Container) pc : pc.getParent();
616                         for (; c != null; c = c.getParent()) {
617                                 LayoutManager layout = c.getLayout();
618                                 if (layout instanceof BoxLayout || layout instanceof OverlayLayout)
619                                         ((LayoutManager2) layout).invalidateLayout(c);
620                         }
621
622                         Dimension prefSize = packable.getPreferredSize();
623                         int targW = constrain(checkParent(packable), packable.getWidth(), prefSize.width, wBounds);
624                         int targH = constrain(checkParent(packable), packable.getHeight(), prefSize.height, hBounds);
625
626                         Point p = packable.isShowing() ? packable.getLocationOnScreen() : packable.getLocation();
627
628                         int x = Math.round(p.x - ((targW - packable.getWidth()) * (1 - lc.getPackWidthAlign())));
629                         int y = Math.round(p.y - ((targH - packable.getHeight()) * (1 - lc.getPackHeightAlign())));
630
631                         if (packable instanceof JPopupMenu) {
632                                 JPopupMenu popupMenu = (JPopupMenu) packable;
633                                 popupMenu.setVisible(false);
634                                 popupMenu.setPopupSize(targW, targH);
635                                 Component invoker = popupMenu.getInvoker();
636                                 Point popPoint = new Point(x, y);
637                                 SwingUtilities.convertPointFromScreen(popPoint, invoker);
638                                 ((JPopupMenu) packable).show(invoker, popPoint.x, popPoint.y);
639
640                                 packable.setPreferredSize(null); // Reset preferred size so we don't read it again.
641
642                         } else {
643                                 packable.setBounds(x, y, targW, targH);
644                         }
645                 }
646         }
647
648         /** Returns a high level window or popup to pack, if any.
649          * @return May be null.
650          */
651         private Container getPackable(Component comp)
652         {
653                 JPopupMenu popup = findType(JPopupMenu.class, comp);
654                 if (popup != null) { // Lightweight/HeavyWeight popup must be handled separately
655                         Container popupComp = popup;
656                         while (popupComp != null) {
657                                 if (popupComp.getClass().getName().contains("HeavyWeightWindow"))
658                                         return popupComp; // Return the heavy weight window for normal processing
659                                 popupComp = popupComp.getParent();
660                         }
661                         return popup; // Return the JPopup.
662                 }
663
664                 return findType(Window.class, comp);
665         }
666
667         public static <E> E findType(Class<E> clazz, Component comp)
668         {
669                 while (comp != null && !clazz.isInstance(comp))
670                         comp = comp.getParent();
671
672                 return (E) comp;
673         }
674
675
676         private int constrain(ContainerWrapper parent, int winSize, int prefSize, BoundSize constrain)
677         {
678                 if (constrain == null)
679                         return winSize;
680
681                 int retSize = winSize;
682                 UnitValue wUV = constrain.getPreferred();
683                 if (wUV != null)
684                         retSize = wUV.getPixels(prefSize, parent, parent);
685
686                 retSize = constrain.constrain(retSize, prefSize, parent);
687
688                 return constrain.getGapPush() ? Math.max(winSize, retSize) : retSize;
689         }
690
691         @Override
692         public Dimension minimumLayoutSize(Container parent)
693         {
694                 synchronized(parent.getTreeLock()) {
695                         return getSizeImpl(parent, LayoutUtil.MIN);
696                 }
697         }
698
699         @Override
700         public Dimension preferredLayoutSize(Container parent)
701         {
702                 synchronized(parent.getTreeLock()) {
703            if (lastParentSize == null || !parent.getSize().equals(lastParentSize)) {
704                for (ComponentWrapper wrapper : ccMap.keySet()) {
705                        if (wrapper.getContentBias() != -1) {
706                        layoutContainer(parent);
707                        break;
708                    }
709                }
710            }
711
712            lastParentSize = parent.getSize();
713            return getSizeImpl(parent, LayoutUtil.PREF);
714                 }
715         }
716
717         @Override
718         public Dimension maximumLayoutSize(Container parent)
719         {
720                 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
721         }
722
723         // Implementation method that does the job.
724         private Dimension getSizeImpl(Container parent, int sizeType)
725         {
726                 checkCache(parent);
727
728                 Insets i = parent.getInsets();
729
730                 int w = LayoutUtil.getSizeSafe(grid != null ? grid.getWidth() : null, sizeType) + i.left + i.right;
731                 int h = LayoutUtil.getSizeSafe(grid != null ? grid.getHeight() : null, sizeType) + i.top + i.bottom;
732
733                 return new Dimension(w, h);
734         }
735
736         @Override
737         public float getLayoutAlignmentX(Container parent)
738         {
739                 return lc != null && lc.getAlignX() != null ? lc.getAlignX().getPixels(1, checkParent(parent), null) : 0;
740         }
741
742         @Override
743         public float getLayoutAlignmentY(Container parent)
744         {
745                 return lc != null && lc.getAlignY() != null ? lc.getAlignY().getPixels(1, checkParent(parent), null) : 0;
746         }
747
748         @Override
749         public void addLayoutComponent(String s, Component comp)
750         {
751                 addLayoutComponent(comp, s);
752         }
753
754         @Override
755         public void addLayoutComponent(Component comp, Object constraints)
756         {
757                 synchronized(comp.getParent().getTreeLock()) {
758                         setComponentConstraintsImpl(comp, constraints, true);
759                 }
760         }
761
762         @Override
763         public void removeLayoutComponent(Component comp)
764         {
765                 synchronized(comp.getParent().getTreeLock()) {
766                         scrConstrMap.remove(comp);
767                         ccMap.remove(new SwingComponentWrapper(comp));
768                         grid = null; // To clear references
769                 }
770         }
771
772         @Override
773         public void invalidateLayout(Container target)
774         {
775                 dirty = true;
776         }
777
778 //      // ************************************************
779 //      // Persistence Delegate and Serializable combined.
780 //      // ************************************************
781 //
782 //      private Object readResolve() throws ObjectStreamException
783 //      {
784 //              return LayoutUtil.getSerializedObject(this);
785 //      }
786 //
787 //      @Override
788 //      public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
789 //      {
790 //              LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in));
791 //      }
792 //
793 //      @Override
794 //      public void writeExternal(ObjectOutput out) throws IOException
795 //      {
796 //              if (getClass() == MigLayout.class)
797 //                      LayoutUtil.writeAsXML(out, this);
798 //      }
799
800         private class MyDebugRepaintListener implements ActionListener
801         {
802                 @Override
803                 public void actionPerformed(ActionEvent e)
804                 {
805                         if (grid != null) {
806                                 Component comp = (Component) grid.getContainer().getComponent();
807                                 if (comp.isShowing()) {
808                                         grid.paintDebug();
809                                         return;
810                                 }
811                         }
812                         debugTimer.stop();
813                         debugTimer = null;
814                 }
815         }
816 }