package net.miginfocom.layout;
/*
* 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
*/
import java.beans.Encoder;
import java.beans.Expression;
import java.beans.PersistenceDelegate;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
public final class UnitValue implements Serializable
{
private static final HashMap<String, Integer> UNIT_MAP = new HashMap<String, Integer>(32);
private static final ArrayList<UnitConverter> CONVERTERS = new ArrayList<UnitConverter>();
/** An operation indicating a static value.
*/
public static final int STATIC = 100;
/** An operation indicating a addition of two sub units.
*/
public static final int ADD = 101; // Must have "sub-unit values"
/** An operation indicating a subtraction of two sub units
*/
public static final int SUB = 102; // Must have "sub-unit values"
/** An operation indicating a multiplication of two sub units.
*/
public static final int MUL = 103; // Must have "sub-unit values"
/** An operation indicating a division of two sub units.
*/
public static final int DIV = 104; // Must have "sub-unit values"
/** An operation indicating the minimum of two sub units
*/
public static final int MIN = 105; // Must have "sub-unit values"
/** An operation indicating the maximum of two sub units
*/
public static final int MAX = 106; // Must have "sub-unit values"
/** An operation indicating the middle value of two sub units
*/
public static final int MID = 107; // Must have "sub-unit values"
/** A unit indicating pixels.
*/
public static final int PIXEL = 0;
/** A unit indicating logical horizontal pixels.
*/
public static final int LPX = 1;
/** A unit indicating logical vertical pixels.
*/
public static final int LPY = 2;
/** A unit indicating millimeters.
*/
public static final int MM = 3;
/** A unit indicating centimeters.
*/
public static final int CM = 4;
/** A unit indicating inches.
*/
public static final int INCH = 5;
/** A unit indicating percent.
*/
public static final int PERCENT = 6;
/** A unit indicating points.
*/
public static final int PT = 7;
/** A unit indicating screen percentage width.
*/
public static final int SPX = 8;
/** A unit indicating screen percentage height.
*/
public static final int SPY = 9;
/** A unit indicating alignment.
*/
public static final int ALIGN = 12;
/** A unit indicating minimum size.
*/
public static final int MIN_SIZE = 13;
/** A unit indicating preferred size.
*/
public static final int PREF_SIZE = 14;
/** A unit indicating maximum size.
*/
public static final int MAX_SIZE = 15;
/** A unit indicating botton size.
*/
public static final int BUTTON = 16;
/** A unit indicating linking to x.
*/
public static final int LINK_X = 18; // First link
/** A unit indicating linking to y.
*/
public static final int LINK_Y = 19;
/** A unit indicating linking to width.
*/
public static final int LINK_W = 20;
/** A unit indicating linking to height.
*/
public static final int LINK_H = 21;
/** A unit indicating linking to x2.
*/
public static final int LINK_X2 = 22;
/** A unit indicating linking to y2.
*/
public static final int LINK_Y2 = 23;
/** A unit indicating linking to x position on screen.
*/
public static final int LINK_XPOS = 24;
/** A unit indicating linking to y position on screen.
*/
public static final int LINK_YPOS = 25; // Last link
/** A unit indicating a lookup.
*/
public static final int LOOKUP = 26;
/** A unit indicating label alignment.
*/
public static final int LABEL_ALIGN = 27;
private static final int IDENTITY = -1;
static {
UNIT_MAP.put("px", PIXEL);
UNIT_MAP.put("lpx", LPX);
UNIT_MAP.put("lpy", LPY);
UNIT_MAP.put("%", PERCENT);
UNIT_MAP.put("cm", CM);
UNIT_MAP.put("in", INCH);
UNIT_MAP.put("spx", SPX);
UNIT_MAP.put("spy", SPY);
UNIT_MAP.put("al", ALIGN);
UNIT_MAP.put("mm", MM);
UNIT_MAP.put("pt", PT);
UNIT_MAP.put("min", MIN_SIZE);
UNIT_MAP.put("minimum", MIN_SIZE);
UNIT_MAP.put("p", PREF_SIZE);
UNIT_MAP.put("pref", PREF_SIZE);
UNIT_MAP.put("max", MAX_SIZE);
UNIT_MAP.put("maximum", MAX_SIZE);
UNIT_MAP.put("button", BUTTON);
UNIT_MAP.put("label", LABEL_ALIGN);
}
static final UnitValue ZERO = new UnitValue(0, null, PIXEL, true, STATIC, null, null, "0px");
static final UnitValue TOP = new UnitValue(0, null, PERCENT, false, STATIC, null, null, "top");
static final UnitValue LEADING = new UnitValue(0, null, PERCENT, true, STATIC, null, null, "leading");
static final UnitValue LEFT = new UnitValue(0, null, PERCENT, true, STATIC, null, null, "left");
static final UnitValue CENTER = new UnitValue(50, null, PERCENT, true, STATIC, null, null, "center");
static final UnitValue TRAILING = new UnitValue(100, null, PERCENT, true, STATIC, null, null, "trailing");
static final UnitValue RIGHT = new UnitValue(100, null, PERCENT, true, STATIC, null, null, "right");
static final UnitValue BOTTOM = new UnitValue(100, null, PERCENT, false, STATIC, null, null, "bottom");
static final UnitValue LABEL = new UnitValue(0, null, LABEL_ALIGN, false, STATIC, null, null, "label");
static final UnitValue INF = new UnitValue(LayoutUtil.INF, null, PIXEL, true, STATIC, null, null, "inf");
static final UnitValue BASELINE_IDENTITY = new UnitValue(0, null, IDENTITY, false, STATIC, null, null, "baseline");
private final transient float value;
private final transient int unit;
private final transient int oper;
private final transient String unitStr;
private transient String linkId = null; // Should be final, but initializes in a sub method.
private final transient boolean isHor;
private final transient UnitValue[] subUnits;
// Pixel
public UnitValue(float value) // If hor/ver does not matter.
{
this(value, null, PIXEL, true, STATIC, null, null, value + "px");
}
public UnitValue(float value, int unit, String createString) // If hor/ver does not matter.
{
this(value, null, unit, true, STATIC, null, null, createString);
}
UnitValue(float value, String unitStr, boolean isHor, int oper, String createString)
{
this(value, unitStr, -1, isHor, oper, null, null, createString);
}
UnitValue(boolean isHor, int oper, UnitValue sub1, UnitValue sub2, String createString)
{
this(0, "", -1, isHor, oper, sub1, sub2, createString);
if (sub1 == null || sub2 == null)
throw new IllegalArgumentException("Sub units is null!");
}
private UnitValue(float value, String unitStr, int unit, boolean isHor, int oper, UnitValue sub1, UnitValue sub2, String createString)
{
if (oper < STATIC || oper > MID)
throw new IllegalArgumentException("Unknown Operation: " + oper);
if (oper >= ADD && oper <= MID && (sub1 == null || sub2 == null))
throw new IllegalArgumentException(oper + " Operation may not have null sub-UnitValues.");
this.value = value;
this.oper = oper;
this.isHor = isHor;
this.unitStr = unitStr;
this.unit = unitStr != null ? parseUnitString() : unit;
this.subUnits = sub1 != null && sub2 != null ? new UnitValue[] {sub1, sub2} : null;
LayoutUtil.putCCString(this, createString); // "this" escapes!! Safe though.
}
/** Returns the size in pixels rounded.
* @param refValue The reference value. Normally the size of the parent. For unit {@link #ALIGN} the current size of the component should be sent in.
* @param parent The parent. May be <code>null</code> for testing the validity of the value, but should normally not and are not
* required to return any usable value if <code>null</code>.
* @param comp The component, if any, that the value is for. Might be <code>null</code> if the value is not
* connected to any component.
* @return The size in pixels.
*/
public final int getPixels(float refValue, ContainerWrapper parent, ComponentWrapper comp)
{
return Math.round(getPixelsExact(refValue, parent, comp));
}
private static final float[] SCALE = new float[] {25.4f, 2.54f, 1f, 0f, 72f};
/** Returns the size in pixels.
* @param refValue The reference value. Normally the size of the parent. For unit {@link #ALIGN} the current size of the component should be sent in.
* @param parent The parent. May be <code>null</code> for testing the validity of the value, but should normally not and are not
* required to return any usable value if <code>null</code>.
* @param comp The component, if any, that the value is for. Might be <code>null</code> if the value is not
* connected to any component.
* @return The size in pixels.
*/
public final float getPixelsExact(float refValue, ContainerWrapper parent, ComponentWrapper comp)
{
if (parent == null)
return 1;
if (oper == STATIC) {
switch (unit) {
case PIXEL:
return value;
case LPX:
case LPY:
return parent.getPixelUnitFactor(unit == LPX) * value;
case MM:
case CM:
case INCH:
case PT:
float f = SCALE[unit - MM];
Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor();
if (s != null)
f *= s;
return (isHor ? parent.getHorizontalScreenDPI() : parent.getVerticalScreenDPI()) * value / f;
case PERCENT:
return value * refValue * 0.01f;
case SPX:
case SPY:
return (unit == SPX ? parent.getScreenWidth() : parent.getScreenHeight()) * value * 0.01f;
case ALIGN:
Integer st = LinkHandler.getValue(parent.getLayout(), "visual", isHor ? LinkHandler.X : LinkHandler.Y);
Integer sz = LinkHandler.getValue(parent.getLayout(), "visual", isHor ? LinkHandler.WIDTH : LinkHandler.HEIGHT);
if (st == null || sz == null)
return 0;
return value * (Math.max(0, sz.intValue()) - refValue) + st;
case MIN_SIZE:
if (comp == null)
return 0;
return isHor ? comp.getMinimumWidth(comp.getHeight()) : comp.getMinimumHeight(comp.getWidth());
case PREF_SIZE:
if (comp == null)
return 0;
return isHor ? comp.getPreferredWidth(comp.getHeight()) : comp.getPreferredHeight(comp.getWidth());
case MAX_SIZE:
if (comp == null)
return 0;
return isHor ? comp.getMaximumWidth(comp.getHeight()) : comp.getMaximumHeight(comp.getWidth());
case BUTTON:
return PlatformDefaults.getMinimumButtonWidth().getPixels(refValue, parent, comp);
case LINK_X:
case LINK_Y:
case LINK_W:
case LINK_H:
case LINK_X2:
case LINK_Y2:
case LINK_XPOS:
case LINK_YPOS:
Integer v = LinkHandler.getValue(parent.getLayout(), getLinkTargetId(), unit - (unit >= LINK_XPOS ? LINK_XPOS : LINK_X));
if (v == null)
return 0;
if (unit == LINK_XPOS)
return parent.getScreenLocationX() + v;
if (unit == LINK_YPOS)
return parent.getScreenLocationY() + v;
return v;
case LOOKUP:
float res = lookup(refValue, parent, comp);
if (res != UnitConverter.UNABLE)
return res;
case LABEL_ALIGN:
return PlatformDefaults.getLabelAlignPercentage() * refValue;
case IDENTITY:
}
throw new IllegalArgumentException("Unknown/illegal unit: " + unit + ", unitStr: " + unitStr);
}
if (subUnits != null && subUnits.length == 2) {
float r1 = subUnits[0].getPixelsExact(refValue, parent, comp);
float r2 = subUnits[1].getPixelsExact(refValue, parent, comp);
switch (oper) {
case ADD:
return r1 + r2;
case SUB:
return r1 - r2;
case MUL:
return r1 * r2;
case DIV:
return r1 / r2;
case MIN:
return r1 < r2 ? r1 : r2;
case MAX:
return r1 > r2 ? r1 : r2;
case MID:
return (r1 + r2) * 0.5f;
}
}
throw new IllegalArgumentException("Internal: Unknown Oper: " + oper);
}
private float lookup(float refValue, ContainerWrapper parent, ComponentWrapper comp)
{
float res = UnitConverter.UNABLE;
for (int i = CONVERTERS.size() - 1; i >= 0; i--) {
res = CONVERTERS.get(i).convertToPixels(value, unitStr, isHor, refValue, parent, comp);
if (res != UnitConverter.UNABLE)
return res;
}
return PlatformDefaults.convertToPixels(value, unitStr, isHor, refValue, parent, comp);
}
private int parseUnitString()
{
int len = unitStr.length();
if (len == 0)
return isHor ? PlatformDefaults.getDefaultHorizontalUnit() : PlatformDefaults.getDefaultVerticalUnit();
Integer u = UNIT_MAP.get(unitStr);
if (u != null)
return u;
if (unitStr.equals("lp"))
return isHor ? LPX : LPY;
if (unitStr.equals("sp"))
return isHor ? SPX : SPY;
if (lookup(0, null, null) != UnitConverter.UNABLE) // To test so we can fail fast
return LOOKUP;
// Only link left. E.g. "otherID.width"
int pIx = unitStr.indexOf('.');
if (pIx != -1) {
linkId = unitStr.substring(0, pIx);
String e = unitStr.substring(pIx + 1);
if (e.equals("x"))
return LINK_X;
if (e.equals("y"))
return LINK_Y;
if (e.equals("w") || e.equals("width"))
return LINK_W;
if (e.equals("h") || e.equals("height"))
return LINK_H;
if (e.equals("x2"))
return LINK_X2;
if (e.equals("y2"))
return LINK_Y2;
if (e.equals("xpos"))
return LINK_XPOS;
if (e.equals("ypos"))
return LINK_YPOS;
}
throw new IllegalArgumentException("Unknown keyword: " + unitStr);
}
final boolean isLinked()
{
return linkId != null;
}
final boolean isLinkedDeep()
{
if (subUnits == null)
return linkId != null;
for (UnitValue subUnit : subUnits) {
if (subUnit.isLinkedDeep())
return true;
}
return false;
}
final String getLinkTargetId()
{
return linkId;
}
final UnitValue getSubUnitValue(int i)
{
return subUnits[i];
}
final int getSubUnitCount()
{
return subUnits != null ? subUnits.length : 0;
}
public final UnitValue[] getSubUnits()
{
return subUnits != null ? subUnits.clone() : null;
}
public final int getUnit()
{
return unit;
}
public final String getUnitString()
{
return unitStr;
}
public final int getOperation()
{
return oper;
}
public final float getValue()
{
return value;
}
public final boolean isHorizontal()
{
return isHor;
}
final public String toString()
{
return getClass().getName() + ". Value=" + value + ", unit=" + unit + ", unitString: " + unitStr + ", oper=" + oper + ", isHor: " + isHor;
}
/** Returns the creation string for this object. Note that {@link LayoutUtil#setDesignTime(ContainerWrapper, boolean)} must be
* set to <code>true</code> for the creation strings to be stored.
* @return The constraint string or <code>null</code> if none is registered.
*/
public final String getConstraintString()
{
return LayoutUtil.getCCString(this);
}
public final int hashCode()
{
return (int) (value * 12345) + (oper >>> 5) + unit >>> 17;
}
/** Adds a global unit converter that can convert from some <code>unit</code> to pixels.
* <p>
* This converter will be asked before the platform converter so the values for it (e.g. "related" and "unrelated")
* can be overridden. It is however not possible to override the built in ones (e.g. "mm", "pixel" or "lp").
* @param conv The converter. Not <code>null</code>.
*/
public synchronized static void addGlobalUnitConverter(UnitConverter conv)
{
if (conv == null)
throw new NullPointerException();
CONVERTERS.add(conv);
}
/** Removed the converter.
* @param unit The converter.
* @return If there was a converter found and thus removed.
*/
public synchronized static boolean removeGlobalUnitConverter(UnitConverter unit)
{
return CONVERTERS.remove(unit);
}
/** Returns the global converters currently registered. The platform converter will not be in this list.
* @return The converters. Never <code>null</code>.
*/
public synchronized static UnitConverter[] getGlobalUnitConverters()
{
return CONVERTERS.toArray(new UnitConverter[CONVERTERS.size()]);
}
/** Returns the current default unit. The default unit is the unit used if no unit is set. E.g. "width 10".
* @return The current default unit.
* @see #PIXEL
* @see #LPX
* @deprecated Use {@link PlatformDefaults#getDefaultHorizontalUnit()} and {@link PlatformDefaults#getDefaultVerticalUnit()} instead.
*/
public static int getDefaultUnit()
{
return PlatformDefaults.getDefaultHorizontalUnit();
}
/** Sets the default unit. The default unit is the unit used if no unit is set. E.g. "width 10".
* @param unit The new default unit.
* @see #PIXEL
* @see #LPX
* @deprecated Use {@link PlatformDefaults#setDefaultHorizontalUnit(int)} and {@link PlatformDefaults#setDefaultVerticalUnit(int)} instead.
*/
public static void setDefaultUnit(int unit)
{
PlatformDefaults.setDefaultHorizontalUnit(unit);
PlatformDefaults.setDefaultVerticalUnit(unit);
}
static {
if(LayoutUtil.HAS_BEANS){
LayoutUtil.setDelegate(UnitValue.class, new PersistenceDelegate() {
protected Expression instantiate(Object oldInstance, Encoder out)
{
UnitValue uv = (UnitValue) oldInstance;
String cs = uv.getConstraintString();
if (cs == null)
throw new IllegalStateException("Design time must be on to use XML persistence. See LayoutUtil.");
return new Expression(oldInstance, ConstraintParser.class, "parseUnitValueOrAlign", new Object[] {
uv.getConstraintString(), (uv.isHorizontal() ? Boolean.TRUE : Boolean.FALSE), null
});
}
});
}
}
// ************************************************
// Persistence Delegate and Serializable combined.
// ************************************************
private static final long serialVersionUID = 1L;
private Object readResolve() throws ObjectStreamException
{
return LayoutUtil.getSerializedObject(this);
}
private void writeObject(ObjectOutputStream out) throws IOException
{
if (getClass() == UnitValue.class)
LayoutUtil.writeAsXML(out, this);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in));
}
}