package net.miginfocom.layout;
import java.beans.*;
import java.io.*;
import java.util.IdentityHashMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
/*
* License (BSD):
* ==============
*
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
* Neither the name of the MiG InfoCom AB nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* @version 1.0
* @author Mikael Grev, MiG InfoCom AB
* Date: 2006-sep-08
*/
/**
* A utility class that has only static helper methods.
*/
public final class LayoutUtil {
/**
* A substitute value for aa really large value. Integer.MAX_VALUE is not
* used since that means a lot of defensive code for potential overflow must
* exist in many places. This value is large enough for being unreasonable
* yet it is hard to overflow.
*/
public static final int INF = (Integer.MAX_VALUE >> 10) - 100; // To reduce
// likelihood
// of
// overflow
// errors
// when
// calculating.
/**
* Tag int for a value that in considered "not set". Used as "null" element
* in int arrays.
*/
static final int NOT_SET = Integer.MIN_VALUE + 12346; // Magic value...
// Index for the different sizes
public static final int MIN = 0;
public static final int PREF = 1;
public static final int MAX = 2;
private static volatile WeakHashMap<Object, String> CR_MAP = null;
private static volatile WeakHashMap<Object, Boolean> DT_MAP = null; // The
// Containers
// that
// have
// design
// time.
// Value
// not
// used.
private static int eSz = 0;
private static int globalDebugMillis = 0;
public static final boolean HAS_BEANS = hasBeans();
private static boolean hasBeans() {
try {
LayoutUtil.class.getClassLoader().loadClass("java.beans.Beans");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private LayoutUtil() {
}
/**
* Returns the current version of MiG Layout.
*
* @return The current version of MiG Layout. E.g. "3.6.3" or "4.0"
*/
public static String getVersion() {
return "4.0";
}
/**
* If global debug should be on or off. If > 0 then debug is turned on
* for all MigLayout instances.
*
* @return The current debug milliseconds.
* @see LC#setDebugMillis(int)
*/
public static int getGlobalDebugMillis() {
return globalDebugMillis;
}
/**
* If global debug should be on or off. If > 0 then debug is turned on
* for all MigLayout instances.
* <p>
* Note! This is a passive value and will be read by panels when the needed,
* which is normally when they repaint/layout.
*
* @param millis
* The new debug milliseconds. 0 turns of global debug and leaves
* debug up to every individual panel.
* @see LC#setDebugMillis(int)
*/
public static void setGlobalDebugMillis(int millis) {
globalDebugMillis = millis;
}
/**
* Sets if design time is turned on for a Container in
* {@link ContainerWrapper}.
*
* @param cw
* The container to set design time for. <code>null</code> is
* legal and can be used as a key to turn on/off design time
* "in general". Note though that design time "in general" is
* always on as long as there is at least one ContainerWrapper
* with design time.
* <p>
* <strong>If this method has not ever been called it will
* default to what <code>Beans.isDesignTime()</code>
* returns.</strong> This means that if you call this method you
* indicate that you will take responsibility for the design time
* value.
* @param b
* <code>true</code> means design time on.
*/
public static void setDesignTime(ContainerWrapper cw, boolean b) {
if (DT_MAP == null)
DT_MAP = new WeakHashMap<Object, Boolean>();
DT_MAP.put((cw != null ? cw.getComponent() : null), b);
}
/**
* Returns if design time is turned on for a Container in
* {@link ContainerWrapper}.
*
* @param cw
* The container to set design time for. <code>null</code> is
* legal will return <code>true</code> if there is at least one
* <code>ContainerWrapper</code> (or <code>null</code>) that have
* design time turned on.
* @return If design time is set for <code>cw</code>.
*/
public static boolean isDesignTime(ContainerWrapper cw) {
if (DT_MAP == null)
return HAS_BEANS && Beans.isDesignTime();
if (cw != null && DT_MAP.containsKey(cw.getComponent()) == false)
cw = null;
Boolean b = DT_MAP.get(cw != null ? cw.getComponent() : null);
return b != null && b;
}
/**
* The size of an empty row or columns in a grid during design time.
*
* @return The number of pixels. Default is 15.
*/
public static int getDesignTimeEmptySize() {
return eSz;
}
/**
* The size of an empty row or columns in a grid during design time.
*
* @param pixels
* The number of pixels. Default is 0 (it was 15 prior to v3.7.2,
* but since that meant different behaviour 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 get the old behaviour.
*/
public static void setDesignTimeEmptySize(int pixels) {
eSz = pixels;
}
/**
* Associates <code>con</code> with the creation string <code>s</code>. The
* <code>con</code> object should probably have an equals method that
* compares identities or <code>con</code> objects that .equals() will only
* be able to have <b>one</b> creation string.
* <p>
* If {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns
* <code>false</code> the method does nothing.
*
* @param con
* The object. if <code>null</code> the method does nothing.
* @param s
* The creation string. if <code>null</code> the method does
* nothing.
*/
static void putCCString(Object con, String s) {
if (s != null && con != null && isDesignTime(null)) {
if (CR_MAP == null)
CR_MAP = new WeakHashMap<Object, String>(64);
CR_MAP.put(con, s);
}
}
/**
* Sets/add the persistence delegates to be used for a class.
*
* @param c
* The class to set the registered deligate for.
* @param del
* The new delegate or <code>null</code> to erase to old one.
*/
static synchronized void setDelegate(Class c, PersistenceDelegate del) {
try {
Introspector.getBeanInfo(c, Introspector.IGNORE_ALL_BEANINFO)
.getBeanDescriptor().setValue("persistenceDelegate", del);
} catch (Exception ignored) {
}
}
/**
* Returns strings set with {@link #putCCString(Object, String)} or
* <code>null</code> if nothing is associated or
* {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns
* <code>false</code>.
*
* @param con
* The constrain object.
* @return The creation string or <code>null</code> if nothing is registered
* with the <code>con</code> object.
*/
static String getCCString(Object con) {
return CR_MAP != null ? CR_MAP.get(con) : null;
}
static void throwCC() {
throw new IllegalStateException(
"setStoreConstraintData(true) must be set for strings to be saved.");
}
/**
* 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 will actually equal
* <code>bounds</code> is dependent om the pref/max sizes and resize
* constraints.
*
* @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) but if the
* array itself is null it will not get any size.
* @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. Can have length less than
* <code>sizes</code> in which case the last element should be
* used for the elements missing.
* @param defPushWeights
* If there is no grow weight for a resConstr the corresponding
* value of this array is used. These forced resConstr will be
* grown last though and only if needed to fill to the bounds.
* @param startSizeType
* The initial size to use. E.g.
* {@link net.miginfocom.layout.LayoutUtil#MIN}.
* @param bounds
* To use for relative sizes.
* @return The sizes. Array length will match <code>sizes</code>.
*/
static int[] calculateSerial(int[][] sizes, ResizeConstraint[] resConstr,
Float[] defPushWeights, int startSizeType, int bounds) {
float[] lengths = new float[sizes.length]; // heights/widths that are
// set
float usedLength = 0.0f;
// Give all preferred size to start with
for (int i = 0; i < sizes.length; i++) {
if (sizes[i] != null) {
float len = sizes[i][startSizeType] != NOT_SET ? sizes[i][startSizeType]
: 0;
int newSizeBounded = getBrokenBoundary(len, sizes[i][MIN],
sizes[i][MAX]);
if (newSizeBounded != NOT_SET)
len = newSizeBounded;
usedLength += len;
lengths[i] = len;
}
}
int useLengthI = Math.round(usedLength);
if (useLengthI != bounds && resConstr != null) {
boolean isGrow = useLengthI < bounds;
// Create a Set with the available priorities
TreeSet<Integer> prioList = new TreeSet<Integer>();
for (int i = 0; i < sizes.length; i++) {
ResizeConstraint resC = (ResizeConstraint) getIndexSafe(
resConstr, i);
if (resC != null)
prioList.add(isGrow ? resC.growPrio : resC.shrinkPrio);
}
Integer[] prioIntegers = prioList.toArray(new Integer[prioList
.size()]);
for (int force = 0; force <= ((isGrow && defPushWeights != null) ? 1
: 0); force++) { // Run twice if defGrow and the need for
// growing.
for (int pr = prioIntegers.length - 1; pr >= 0; pr--) {
int curPrio = prioIntegers[pr];
float totWeight = 0f;
Float[] resizeWeight = new Float[sizes.length];
for (int i = 0; i < sizes.length; i++) {
if (sizes[i] == null) // if no min/pref/max size at all
// do not grow or shrink.
continue;
ResizeConstraint resC = (ResizeConstraint) getIndexSafe(
resConstr, i);
if (resC != null) {
int prio = isGrow ? resC.growPrio : resC.shrinkPrio;
if (curPrio == prio) {
if (isGrow) {
resizeWeight[i] = (force == 0 || resC.grow != null) ? resC.grow
: (defPushWeights[i < defPushWeights.length ? i
: defPushWeights.length - 1]);
} else {
resizeWeight[i] = resC.shrink;
}
if (resizeWeight[i] != null)
totWeight += resizeWeight[i];
}
}
}
if (totWeight > 0f) {
boolean hit;
do {
float toChange = bounds - usedLength;
hit = false;
float changedWeight = 0f;
for (int i = 0; i < sizes.length
&& totWeight > 0.0001f; i++) {
Float weight = resizeWeight[i];
if (weight != null) {
float sizeDelta = toChange * weight
/ totWeight;
float newSize = lengths[i] + sizeDelta;
if (sizes[i] != null) {
int newSizeBounded = getBrokenBoundary(
newSize, sizes[i][MIN],
sizes[i][MAX]);
if (newSizeBounded != NOT_SET) {
resizeWeight[i] = null;
hit = true;
changedWeight += weight;
newSize = newSizeBounded;
sizeDelta = newSize - lengths[i];
}
}
lengths[i] = newSize;
usedLength += sizeDelta;
}
}
totWeight -= changedWeight;
} while (hit);
}
}
}
}
return roundSizes(lengths);
}
static Object getIndexSafe(Object[] arr, int ix) {
return arr != null ? arr[ix < arr.length ? ix : arr.length - 1] : null;
}
/**
* Returns the broken boundary if <code>sz</code> is outside the boundaries
* <code>lower</code> or <code>upper</code>. If both boundaries are broken,
* the lower one is returned. If <code>sz</code> is < 0 then
* <code>new Float(0f)</code> is returned so that no sizes can be negative.
*
* @param sz
* The size to check
* @param lower
* The lower boundary (or <code>null</code> fo no boundary).
* @param upper
* The upper boundary (or <code>null</code> fo no boundary).
* @return The broken boundary.
*/
private static int getBrokenBoundary(float sz, int lower, int upper) {
if (lower != NOT_SET) {
if (sz < lower)
return lower;
} else if (sz < 0f) {
return 0;
}
if (upper != NOT_SET && sz > upper)
return upper;
return NOT_SET;
}
static int sum(int[] terms, int start, int len) {
int s = 0;
for (int i = start, iSz = start + len; i < iSz; i++)
s += terms[i];
return s;
}
static int sum(int[] terms) {
return sum(terms, 0, terms.length);
}
public static int getSizeSafe(int[] sizes, int sizeType) {
if (sizes == null || sizes[sizeType] == NOT_SET)
return sizeType == MAX ? LayoutUtil.INF : 0;
return sizes[sizeType];
}
static BoundSize derive(BoundSize bs, UnitValue min, UnitValue pref,
UnitValue max) {
if (bs == null || bs.isUnset())
return new BoundSize(min, pref, max, null);
return new BoundSize(min != null ? min : bs.getMin(),
pref != null ? pref : bs.getPreferred(), max != null ? max
: bs.getMax(), bs.getGapPush(), null);
}
/**
* Returns if left-to-right orientation is used. If not set explicitly in
* the layout constraints the Locale of the <code>parent</code> is used.
*
* @param lc
* The constraint if there is one. Can be <code>null</code>.
* @param container
* The parent that may be used to get the left-to-right if ffc
* does not specify this.
* @return If left-to-right orientation is currently used.
*/
public static boolean isLeftToRight(LC lc, ContainerWrapper container) {
if (lc != null && lc.getLeftToRight() != null)
return lc.getLeftToRight();
return container == null || container.isLeftToRight();
}
/**
* Round a number of float sizes into int sizes so that the total length
* match up
*
* @param sizes
* The sizes to round
* @return An array of equal length as <code>sizes</code>.
*/
static int[] roundSizes(float[] sizes) {
int[] retInts = new int[sizes.length];
float posD = 0;
for (int i = 0; i < retInts.length; i++) {
int posI = (int) (posD + 0.5f);
posD += sizes[i];
retInts[i] = (int) (posD + 0.5f) - posI;
}
return retInts;
}
/**
* Safe equals. null == null, but null never equals anything else.
*
* @param o1
* The first object. May be <code>null</code>.
* @param o2
* The second object. May be <code>null</code>.
* @return Returns <code>true</code> if <code>o1</code> and <code>o2</code>
* are equal (using .equals()) or both are <code>null</code>.
*/
static boolean equals(Object o1, Object o2) {
return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
}
// static int getBaselineCorrect(Component comp)
// {
// Dimension pSize = comp.getPreferredSize();
// int baseline = comp.getBaseline(pSize.width, pSize.height);
// int nextBaseline = comp.getBaseline(pSize.width, pSize.height + 1);
//
// // Amount to add to height when calculating where baseline
// // lands for a particular height:
// int padding = 0;
//
// // Where the baseline is relative to the mid point
// int baselineOffset = baseline - pSize.height / 2;
// if (pSize.height % 2 == 0 && baseline != nextBaseline) {
// padding = 1;
// } else if (pSize.height % 2 == 1 && baseline == nextBaseline) {
// baselineOffset--;
// padding = 1;
// }
//
// // The following calculates where the baseline lands for
// // the height z:
// return (pSize.height + padding) / 2 + baselineOffset;
// }
/**
* Returns the inset for the side.
*
* @param side
* top == 0, left == 1, bottom = 2, right = 3.
* @param getDefault
* If <code>true</code> the default insets will get retrieved if
* <code>lc</code> has none set.
* @return The inset for the side. Never <code>null</code>.
*/
static UnitValue getInsets(LC lc, int side, boolean getDefault) {
UnitValue[] i = lc.getInsets();
return (i != null && i[side] != null) ? i[side]
: (getDefault ? PlatformDefaults.getPanelInsets(side)
: UnitValue.ZERO);
}
/**
* Writes the objet and CLOSES the stream. Uses the persistence delegate
* registered in this class.
*
* @param os
* The stream to write to. Will be closed.
* @param o
* The object to be serialized.
* @param listener
* The listener to recieve the exeptions if there are any. If
* <code>null</code> not used.
*/
static void writeXMLObject(OutputStream os, Object o,
ExceptionListener listener) {
ClassLoader oldClassLoader = Thread.currentThread()
.getContextClassLoader();
Thread.currentThread().setContextClassLoader(
LayoutUtil.class.getClassLoader());
XMLEncoder encoder = new XMLEncoder(os);
if (listener != null)
encoder.setExceptionListener(listener);
encoder.writeObject(o);
encoder.close(); // Must be closed to write.
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
private static ByteArrayOutputStream writeOutputStream = null;
/**
* Writes an object to XML.
*
* @param out
* The boject out to write to. Will not be closed.
* @param o
* The object to write.
*/
public static synchronized void writeAsXML(ObjectOutput out, Object o)
throws IOException {
if (writeOutputStream == null)
writeOutputStream = new ByteArrayOutputStream(16384);
writeOutputStream.reset();
writeXMLObject(writeOutputStream, o, new ExceptionListener() {
public void exceptionThrown(Exception e) {
e.printStackTrace();
}
});
byte[] buf = writeOutputStream.toByteArray();
out.writeInt(buf.length);
out.write(buf);
}
private static byte[] readBuf = null;
/**
* Reads an object from <code>in</code> using the
*
* @param in
* The object input to read from.
* @return The object. Never <code>null</code>.
* @throws IOException
* If there was a problem saving as XML
*/
public static synchronized Object readAsXML(ObjectInput in)
throws IOException {
if (readBuf == null)
readBuf = new byte[16384];
Thread cThread = Thread.currentThread();
ClassLoader oldCL = null;
try {
oldCL = cThread.getContextClassLoader();
cThread.setContextClassLoader(LayoutUtil.class.getClassLoader());
} catch (SecurityException ignored) {
}
Object o = null;
try {
int length = in.readInt();
if (length > readBuf.length)
readBuf = new byte[length];
in.readFully(readBuf, 0, length);
o = new XMLDecoder(new ByteArrayInputStream(readBuf, 0, length))
.readObject();
} catch (EOFException ignored) {
}
if (oldCL != null)
cThread.setContextClassLoader(oldCL);
return o;
}
private static final IdentityHashMap<Object, Object> SER_MAP = new IdentityHashMap<Object, Object>(
2);
/**
* Sets the serialized object and associates it with <code>caller</code>.
*
* @param caller
* The object created <code>o</code>
* @param o
* The just serialized object.
*/
public static void setSerializedObject(Object caller, Object o) {
synchronized (SER_MAP) {
SER_MAP.put(caller, o);
}
}
/**
* Returns the serialized object that are associated with
* <code>caller</code>. It also removes it from the list.
*
* @param caller
* The original creator of the object.
* @return The object.
*/
public static Object getSerializedObject(Object caller) {
synchronized (SER_MAP) {
return SER_MAP.remove(caller);
}
}
}