Merge branch 'Jalview-JS/jim/JAL-3253-JAL-3418' into Jalview-JS/JAL-3253-applet
[jalview.git] / srcjar / net / miginfocom / layout / LayoutUtil.java
1 package net.miginfocom.layout;
2
3 import java.io.IOException;
4 import java.io.ObjectInput;
5 import java.io.ObjectOutput;
6 import java.util.TreeSet;
7 import java.util.WeakHashMap;
8 /*
9  * License (BSD):
10  * ==============
11  *
12  * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
13  * All rights reserved.
14  *
15  * Redistribution and use in source and binary forms, with or without modification,
16  * are permitted provided that the following conditions are met:
17  * Redistributions of source code must retain the above copyright notice, this list
18  * of conditions and the following disclaimer.
19  * Redistributions in binary form must reproduce the above copyright notice, this
20  * list of conditions and the following disclaimer in the documentation and/or other
21  * materials provided with the distribution.
22  * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
23  * used to endorse or promote products derived from this software without specific
24  * prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
32  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
35  * OF SUCH DAMAGE.
36  *
37  * @version 1.0
38  * @author Mikael Grev, MiG InfoCom AB
39  *         Date: 2006-sep-08
40  */
41
42 /** A utility class that has only static helper methods.
43  */
44 public final class LayoutUtil
45 {
46         /** A substitute value for a really large value. Integer.MAX_VALUE is not used since that means a lot of defensive code
47          * for potential overflow must exist in many places. This value is large enough for being unreasonable yet it is hard to
48          * overflow.
49          */
50         public static final int INF = (Integer.MAX_VALUE >> 10) - 100; // To reduce likelihood of overflow errors when calculating.
51
52         /** Tag int for a value that in considered "not set". Used as "null" element in int arrays.
53          */
54         static final int NOT_SET = Integer.MIN_VALUE + 12346;   // Magic value...
55
56         // Index for the different sizes
57         public static final int MIN = 0;
58         public static final int PREF = 1;
59         public static final int MAX = 2;
60
61         public static final int HORIZONTAL = 0;
62         public static final int VERTICAL = 1;
63
64         private static volatile WeakHashMap<Object, String> CR_MAP = null;
65         private static volatile WeakHashMap<Object, Boolean> DT_MAP = null;      // The Containers that have design time. Value not used.
66         private static int eSz = 0;
67         private static int globalDebugMillis = 0;
68     public static final boolean HAS_BEANS = hasBeans();
69
70     private static boolean hasBeans()
71     {
72 //        try {
73 //            LayoutUtil.class.getClassLoader().loadClass("java.beans.Beans");
74 //            return true;
75 //        } catch (Throwable e) {
76             return false;
77 //        }
78     }
79
80         private LayoutUtil()
81         {
82         }
83
84         /** Returns the current version of MiG Layout.
85          * @return The current version of MiG Layout. E.g. "3.6.3" or "4.0"
86          */
87         public static String getVersion()
88         {
89                 return "5.0";
90         }
91
92         /** If global debug should be on or off. If &gt; 0 then debug is turned on for all MigLayout
93          * instances.
94          * @return The current debug milliseconds.
95          * @see LC#setDebugMillis(int)
96          */
97         public static int getGlobalDebugMillis()
98         {
99                 return globalDebugMillis;
100         }
101
102         /** If global debug should be on or off. If &gt; 0 then debug is turned on for all MigLayout
103          * instances.
104          * <p>
105          * Note! This is a passive value and will be read by panels when the needed, which is normally
106          * when they repaint/layout.
107          * @param millis The new debug milliseconds. 0 turns of global debug and leaves debug up to every
108          * individual panel.
109          * @see LC#setDebugMillis(int)
110          */
111         public static void setGlobalDebugMillis(int millis)
112         {
113                 globalDebugMillis = millis;
114         }
115
116         /** Sets if design time is turned on for a Container in {@link ContainerWrapper}.
117          * @param cw The container to set design time for. <code>null</code> is legal and can be used as
118          * a key to turn on/off design time "in general". Note though that design time "in general" is
119          * always on as long as there is at least one ContainerWrapper with design time.
120          * <p>
121          * <strong>If this method has not ever been called it will default to what
122          * <code>Beans.isDesignTime()</code> returns.</strong> This means that if you call
123          * this method you indicate that you will take responsibility for the design time value.
124          * @param b <code>true</code> means design time on.
125          */
126         public static void setDesignTime(ContainerWrapper cw, boolean b)
127         {
128                 if (DT_MAP == null)
129                         DT_MAP = new WeakHashMap<Object, Boolean>();
130
131                 DT_MAP.put((cw != null ? cw.getComponent() : null), b);
132         }
133
134         /** Returns if design time is turned on for a Container in {@link ContainerWrapper}.
135          * @param cw The container to set design time for. <code>null</code> is legal will return <code>true</code>
136          * if there is at least one <code>ContainerWrapper</code> (or <code>null</code>) that have design time
137          * turned on.
138          * @return If design time is set for <code>cw</code>.
139          */
140         public static boolean isDesignTime(ContainerWrapper cw)
141         {
142                 if (DT_MAP == null)
143                         return false;//HAS_BEANS && Beans.isDesignTime();
144
145                 // assume design time "in general" (cw is null) if there is at least one container with design time
146                 // (for storing constraints creation strings in method putCCString())
147                 if (cw == null && DT_MAP != null && !DT_MAP.isEmpty() )
148                         return true;
149
150                 if (cw != null && DT_MAP.containsKey(cw.getComponent()) == false)
151                         cw = null;
152
153                 Boolean b = DT_MAP.get(cw != null ? cw.getComponent() : null);
154                 return b != null && b;
155         }
156
157         /** The size of an empty row or columns in a grid during design time.
158          * @return The number of pixels. Default is 15.
159          */
160         public static int getDesignTimeEmptySize()
161         {
162                 return eSz;
163         }
164
165         /** The size of an empty row or columns in a grid during design time.
166          * @param pixels The number of pixels. Default is 0 (it was 15 prior to v3.7.2, but since that meant different behaviour
167          * under design time by default it was changed to be 0, same as non-design time). IDE vendors can still set it to 15 to
168          * get the old behaviour.
169          */
170         public static void setDesignTimeEmptySize(int pixels)
171         {
172                 eSz = pixels;
173         }
174
175         /** Associates <code>con</code> with the creation string <code>s</code>. The <code>con</code> object should
176          * probably have an equals method that compares identities or <code>con</code> objects that .equals() will only
177          * be able to have <b>one</b> creation string.
178          * <p>
179          * If {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns <code>false</code> the method does nothing.
180          * @param con The object. if <code>null</code> the method does nothing.
181          * @param s The creation string. if <code>null</code> the method does nothing.
182          */
183         static void putCCString(Object con, String s)
184         {
185                 if (s != null && con != null && isDesignTime(null)) {
186                         if (CR_MAP == null)
187                                 CR_MAP = new WeakHashMap<Object, String>(64);
188
189                         CR_MAP.put(con, s);
190                 }
191         }
192
193 //      /** Sets/add the persistence delegates to be used for a class.
194 //       * @param c The class to set the registered delegate for.
195 //       * @param del The new delegate or <code>null</code> to erase to old one.
196 //       */
197 //      static synchronized void setDelegate(Class<?> c, PersistenceDelegate del)
198 //      {
199 //              try {
200 //                      Introspector.getBeanInfo(c, Introspector.IGNORE_ALL_BEANINFO).getBeanDescriptor().setValue("persistenceDelegate", del);
201 //              } catch (Exception ignored) {
202 //              }
203 //      }
204
205         /** Returns strings set with {@link #putCCString(Object, String)} or <code>null</code> if nothing is associated or
206          * {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns <code>false</code>.
207          * @param con The constrain object.
208          * @return The creation string or <code>null</code> if nothing is registered with the <code>con</code> object.
209          */
210         static String getCCString(Object con)
211         {
212                 return CR_MAP != null ? CR_MAP.get(con) : null;
213         }
214
215         static void throwCC()
216         {
217                 throw new IllegalStateException("setStoreConstraintData(true) must be set for strings to be saved.");
218         }
219
220         /** Takes a number on min/preferred/max sizes and resize constraints and returns the calculated sizes which sum should add up to <code>bounds</code>. Whether the sum
221          * will actually equal <code>bounds</code> is dependent on the pref/max sizes and resize constraints.
222          * @param sizes [ix],[MIN][PREF][MAX]. Grid.CompWrap.NOT_SET will be treated as N/A or 0. A "[MIN][PREF][MAX]" array with null elements will be interpreted as very flexible (no bounds)
223          * but if the array itself is null it will not get any size.
224          * @param resConstr Elements can be <code>null</code> and the whole array can be <code>null</code>. <code>null</code> means that the size will not be flexible at all.
225          * Can have length less than <code>sizes</code> in which case the last element should be used for the elements missing.
226          * @param defPushWeights If there is no grow weight for a resConstr the corresponding value of this array is used.
227          * These forced resConstr will be grown last though and only if needed to fill to the bounds.
228          * @param startSizeType The initial size to use. E.g. {@link net.miginfocom.layout.LayoutUtil#MIN}.
229          * @param bounds To use for relative sizes.
230          * @return The sizes. Array length will match <code>sizes</code>.
231          */
232         static int[] calculateSerial(int[][] sizes, ResizeConstraint[] resConstr, Float[] defPushWeights, int startSizeType, int bounds)
233         {
234                 float[] lengths = new float[sizes.length];      // heights/widths that are set
235                 float usedLength = 0.0f;
236
237                 // Give all preferred size to start with
238                 for (int i = 0; i < sizes.length; i++) {
239                         if (sizes[i] != null) {
240                                 float len = sizes[i][startSizeType] != NOT_SET ? sizes[i][startSizeType] : 0;
241                                 int newSizeBounded = getBrokenBoundary(len, sizes[i][MIN], sizes[i][MAX]);
242                                 if (newSizeBounded != NOT_SET)
243                                         len = newSizeBounded;
244
245                                 usedLength += len;
246                                 lengths[i] = len;
247                         }
248                 }
249
250                 int useLengthI = Math.round(usedLength);
251                 if (useLengthI != bounds && resConstr != null) {
252                         boolean isGrow = useLengthI < bounds;
253
254                         // Create a Set with the available priorities
255                         TreeSet<Integer> prioList = new TreeSet<Integer>();
256                         for (int i = 0; i < sizes.length; i++) {
257                                 ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i);
258                                 if (resC != null)
259                                         prioList.add(isGrow ? resC.growPrio : resC.shrinkPrio);
260                         }
261                         Integer[] prioIntegers = prioList.toArray(new Integer[prioList.size()]);
262
263                         for (int force = 0; force <= ((isGrow && defPushWeights != null) ? 1 : 0); force++) {    // Run twice if defGrow and the need for growing.
264                                 for (int pr = prioIntegers.length - 1; pr >= 0; pr--) {
265                                         int curPrio = prioIntegers[pr];
266
267                                         float totWeight = 0f;
268                                         Float[] resizeWeight = new Float[sizes.length];
269                                         for (int i = 0; i < sizes.length; i++) {
270                                                 if (sizes[i] == null)   // if no min/pref/max size at all do not grow or shrink.
271                                                         continue;
272
273                                                 ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i);
274                                                 if (resC != null) {
275                                                         int prio = isGrow ? resC.growPrio : resC.shrinkPrio;
276
277                                                         if (curPrio == prio) {
278                                                                 if (isGrow) {
279                                                                         resizeWeight[i] = (force == 0 || resC.grow != null) ? resC.grow : (defPushWeights[i < defPushWeights.length ? i : defPushWeights.length - 1]);
280                                                                 } else {
281                                                                         resizeWeight[i] = resC.shrink;
282                                                                 }
283                                                                 if (resizeWeight[i] != null)
284                                                                         totWeight += resizeWeight[i];
285                                                         }
286                                                 }
287                                         }
288
289                                         if (totWeight > 0f) {
290                                                 boolean hit;
291                                                 do {
292                                                         float toChange = bounds - usedLength;
293                                                         hit = false;
294                                                         float changedWeight = 0f;
295                                                         for (int i = 0; i < sizes.length && totWeight > 0.0001f; i++) {
296
297                                                                 Float weight = resizeWeight[i];
298                                                                 if (weight != null) {
299                                                                         float sizeDelta = toChange * weight / totWeight;
300                                                                         float newSize = lengths[i] + sizeDelta;
301
302                                                                         if (sizes[i] != null) {
303                                                                                 int newSizeBounded = getBrokenBoundary(newSize, sizes[i][MIN], sizes[i][MAX]);
304                                                                                 if (newSizeBounded != NOT_SET) {
305                                                                                         resizeWeight[i] = null;
306                                                                                         hit = true;
307                                                                                         changedWeight += weight;
308                                                                                         newSize = newSizeBounded;
309                                                                                         sizeDelta = newSize - lengths[i];
310                                                                                 }
311                                                                         }
312
313                                                                         lengths[i] = newSize;
314                                                                         usedLength += sizeDelta;
315                                                                 }
316                                                         }
317                                                         totWeight -= changedWeight;
318                                                 } while (hit);
319                                         }
320                                 }
321                         }
322                 }
323                 return roundSizes(lengths);
324         }
325
326         static Object getIndexSafe(Object[] arr, int ix)
327         {
328                 return arr != null ? arr[ix < arr.length ? ix : arr.length - 1] : null;
329         }
330
331         /** Returns the broken boundary if <code>sz</code> is outside the boundaries <code>lower</code> or <code>upper</code>. If both boundaries
332          * are broken, the lower one is returned. If <code>sz</code> is &lt; 0 then <code>new Float(0f)</code> is returned so that no sizes can be
333          * negative.
334          * @param sz The size to check
335          * @param lower The lower boundary (or <code>null</code> for no boundary).
336          * @param upper The upper boundary (or <code>null</code> for no boundary).
337          * @return The broken boundary.
338          */
339         private static int getBrokenBoundary(float sz, int lower, int upper)
340         {
341                 if (lower != NOT_SET) {
342                         if (sz < lower)
343                                 return lower;
344                 } else if (sz < 0f) {
345                         return 0;
346                 }
347
348                 if (upper != NOT_SET && sz > upper)
349                         return upper;
350
351                 return NOT_SET;
352         }
353
354
355         static int sum(int[] terms, int start, int len)
356         {
357                 int s = 0;
358                 for (int i = start, iSz = start + len; i < iSz; i++)
359                         s += terms[i];
360                 return s;
361         }
362
363         static int sum(int[] terms)
364         {
365                 return sum(terms, 0, terms.length);
366         }
367
368         /** Keeps f within min and max. Min is of higher priority if min is larger than max.
369          * @param f The value to clamp
370          * @param min
371          * @param max
372          * @return The clamped value, between min and max.
373          */
374         static float clamp(float f, float min, float max)
375         {
376                 return Math.max(min, Math.min(f, max));
377         }
378
379         /** Keeps i within min and max. Min is of higher priority if min is larger than max.
380          * @param i The value to clamp
381          * @param min
382          * @param max
383          * @return The clamped value, between min and max.
384          */
385         static int clamp(int i, int min, int max)
386         {
387                 return Math.max(min, Math.min(i, max));
388         }
389
390         public static int getSizeSafe(int[] sizes, int sizeType)
391         {
392                 if (sizes == null || sizes[sizeType] == NOT_SET)
393                         return sizeType == MAX ? LayoutUtil.INF : 0;
394                 return sizes[sizeType];
395         }
396
397         static BoundSize derive(BoundSize bs, UnitValue min, UnitValue pref, UnitValue max)
398         {
399                 if (bs == null || bs.isUnset())
400                         return new BoundSize(min, pref, max, null);
401
402                 return new BoundSize(
403                                 min != null ? min : bs.getMin(),
404                                 pref != null ? pref : bs.getPreferred(),
405                                 max != null ? max : bs.getMax(),
406                                 bs.getGapPush(),
407                                 null);
408         }
409
410         /** Returns if left-to-right orientation is used. If not set explicitly in the layout constraints the Locale
411          * of the <code>parent</code> is used.
412          * @param lc The constraint if there is one. Can be <code>null</code>.
413          * @param container The parent that may be used to get the left-to-right if lc does not specify this.
414          * @return If left-to-right orientation is currently used.
415          */
416         public static boolean isLeftToRight(LC lc, ContainerWrapper container)
417         {
418                 if (lc != null && lc.getLeftToRight() != null)
419                         return lc.getLeftToRight();
420
421                 return container == null || container.isLeftToRight();
422         }
423
424         /** Round a number of float sizes into int sizes so that the total length match up
425          * @param sizes The sizes to round
426          * @return An array of equal length as <code>sizes</code>.
427          */
428         static int[] roundSizes(float[] sizes)
429         {
430                 int[] retInts = new int[sizes.length];
431                 float posD = 0;
432
433                 for (int i = 0; i < retInts.length; i++) {
434                         int posI = (int) (posD + 0.5f);
435
436                         posD += sizes[i];
437
438                         retInts[i] = (int) (posD + 0.5f) - posI;
439                 }
440
441                 return retInts;
442         }
443
444         /** Safe equals. null == null, but null never equals anything else.
445          * @param o1 The first object. May be <code>null</code>.
446          * @param o2 The second object. May be <code>null</code>.
447          * @return Returns <code>true</code> if <code>o1</code> and <code>o2</code> are equal (using .equals()) or both are <code>null</code>.
448          */
449         static boolean equals(Object o1, Object o2)
450         {
451                 return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
452         }
453
454 //      static int getBaselineCorrect(Component comp)
455 //      {
456 //              Dimension pSize = comp.getPreferredSize();
457 //              int baseline = comp.getBaseline(pSize.width, pSize.height);
458 //              int nextBaseline = comp.getBaseline(pSize.width, pSize.height + 1);
459 //
460 //              // Amount to add to height when calculating where baseline
461 //              // lands for a particular height:
462 //              int padding = 0;
463 //
464 //              // Where the baseline is relative to the mid point
465 //              int baselineOffset = baseline - pSize.height / 2;
466 //              if (pSize.height % 2 == 0 && baseline != nextBaseline) {
467 //                      padding = 1;
468 //              } else if (pSize.height % 2 == 1 && baseline == nextBaseline) {
469 //                      baselineOffset--;
470 //                      padding = 1;
471 //              }
472 //
473 //              // The following calculates where the baseline lands for
474 //              // the height z:
475 //              return (pSize.height + padding) / 2 + baselineOffset;
476 //      }
477
478
479         /** Returns the insets for the side.
480          * @param side top == 0, left == 1, bottom = 2, right = 3.
481          * @param getDefault If <code>true</code> the default insets will get retrieved if <code>lc</code> has none set.
482          * @return The insets for the side. Never <code>null</code>.
483          */
484         static UnitValue getInsets(LC lc, int side, boolean getDefault)
485         {
486                 UnitValue[] i = lc.getInsets();
487                 return (i != null && i[side] != null) ? i[side] : (getDefault ? PlatformDefaults.getPanelInsets(side) : UnitValue.ZERO);
488         }
489
490 //      /** Writes the object and CLOSES the stream. Uses the persistence delegate registered in this class.
491 //       * @param os The stream to write to. Will be closed.
492 //       * @param o The object to be serialized.
493 //       * @param listener The listener to receive the exceptions if there are any. If <code>null</code> not used.
494 //       */
495 //      static void writeXMLObject(OutputStream os, Object o, ExceptionListener listener)
496 //      {
497 //              ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
498 //              Thread.currentThread().setContextClassLoader(LayoutUtil.class.getClassLoader());
499 //
500 //              XMLEncoder encoder = new XMLEncoder(os);
501 //
502 //              if (listener != null)
503 //                      encoder.setExceptionListener(listener);
504 //
505 //              encoder.writeObject(o);
506 //        encoder.close();    // Must be closed to write.
507 //
508 //              Thread.currentThread().setContextClassLoader(oldClassLoader);
509 //      }
510
511 //      private static ByteArrayOutputStream writeOutputStream = null;
512 //      /** Writes an object to XML.
513 //       * @param out The object out to write to. Will not be closed.
514 //       * @param o The object to write.
515 //       */
516         public static synchronized void writeAsXML(ObjectOutput out, Object o) throws IOException
517         {
518 //              if (writeOutputStream == null)
519 //                      writeOutputStream = new ByteArrayOutputStream(16384);
520 //
521 //              writeOutputStream.reset();
522 //
523 //              writeXMLObject(writeOutputStream, o, new ExceptionListener() {
524 //                      @Override
525 //                      public void exceptionThrown(Exception e) {
526 //                              e.printStackTrace();
527 //                      }});
528 //
529 //              byte[] buf = writeOutputStream.toByteArray();
530 //
531 //              out.writeInt(buf.length);
532 //              out.write(buf);
533         }
534 //
535 //      private static byte[] readBuf = null;
536         /** Reads an object from <code>in</code> using the
537          * @param in The object input to read from.
538          * @return The object. Never <code>null</code>.
539          * @throws IOException If there was a problem saving as XML
540          */
541         public static synchronized Object readAsXML(ObjectInput in) throws IOException
542         {
543 //              if (readBuf == null)
544 //                      readBuf = new byte[16384];
545 //
546 //              Thread f cThread = Thread.currentThread();
547 //              ClassLoader oldCL = null;
548 //
549 //              try {
550 //                      oldCL = cThread.getContextClassLoader();
551 //                      cThread.setContextClassLoader(LayoutUtil.class.getClassLoader());
552 //              } catch(SecurityException ignored) {
553 //              }
554 //
555 //              Object o = null;
556 //              try {
557 //                      int length = in.readInt();
558 //                      if (length > readBuf.length)
559 //                              readBuf = new byte[length];
560 //
561 //                      in.readFully(readBuf, 0, length);
562 //
563 //                      o = new XMLDecoder(new ByteArrayInputStream(readBuf, 0, length)).readObject();
564 //
565 //              } catch(EOFException ignored) {
566 //              }
567 //
568 //              if (oldCL != null)
569 //                      cThread.setContextClassLoader(oldCL);
570 //
571 //              return o;
572           return null;
573         }
574 //
575 //      private static final IdentityHashMap<Object, Object> SER_MAP = new IdentityHashMap<Object, Object>(2);
576 //
577 //      /** Sets the serialized object and associates it with <code>caller</code>.
578 //       * @param caller The object created <code>o</code>
579 //       * @param o The just serialized object.
580 //       */
581         public static void setSerializedObject(Object caller, Object o)
582         {
583 //              synchronized(SER_MAP) {
584 //                      SER_MAP.put(caller, o);
585 //              }
586         }
587
588         /** Returns the serialized object that are associated with <code>caller</code>. It also removes it from the list.
589          * @param caller The original creator of the object.
590          * @return The object.
591          */
592         public static Object getSerializedObject(Object caller)
593         {
594           return null;
595 //              synchronized(SER_MAP) {
596 //                      return SER_MAP.remove(caller);
597 //              }
598         }
599 }