diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..6c06bcb --- /dev/null +++ b/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..cc390c0 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + glum + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/buildGlumBin.jardesc b/buildGlumBin.jardesc new file mode 100644 index 0000000..42edf92 --- /dev/null +++ b/buildGlumBin.jardesc @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/buildGlumSrc.jardesc b/buildGlumSrc.jardesc new file mode 100644 index 0000000..5f19168 --- /dev/null +++ b/buildGlumSrc.jardesc @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/dockingFramesCore-src.jar b/lib/dockingFramesCore-src.jar new file mode 100644 index 0000000..742b1f9 Binary files /dev/null and b/lib/dockingFramesCore-src.jar differ diff --git a/lib/dockingFramesCore.jar b/lib/dockingFramesCore.jar new file mode 100644 index 0000000..0ef9fe9 Binary files /dev/null and b/lib/dockingFramesCore.jar differ diff --git a/lib/guava-12.0-sources.jar b/lib/guava-12.0-sources.jar new file mode 100644 index 0000000..f6e0301 Binary files /dev/null and b/lib/guava-12.0-sources.jar differ diff --git a/lib/guava-12.0.jar b/lib/guava-12.0.jar new file mode 100644 index 0000000..fefd6b2 Binary files /dev/null and b/lib/guava-12.0.jar differ diff --git a/lib/miglayout-3.7.2-sources.jar b/lib/miglayout-3.7.2-sources.jar new file mode 100644 index 0000000..a71e8c8 Binary files /dev/null and b/lib/miglayout-3.7.2-sources.jar differ diff --git a/lib/miglayout-3.7.2-swing.jar b/lib/miglayout-3.7.2-swing.jar new file mode 100755 index 0000000..f4b0246 Binary files /dev/null and b/lib/miglayout-3.7.2-swing.jar differ diff --git a/src/Manifest.txt b/src/Manifest.txt new file mode 100644 index 0000000..fcb7d37 --- /dev/null +++ b/src/Manifest.txt @@ -0,0 +1 @@ +Class-Path: lib/guava-12.0.jar lib/miglayout-3.7.2-sources.jar diff --git a/src/glum/coord/Convert.java b/src/glum/coord/Convert.java new file mode 100644 index 0000000..ad5af9b --- /dev/null +++ b/src/glum/coord/Convert.java @@ -0,0 +1,95 @@ +package glum.coord; + +/** + * Contains conversion multipliers to/from feet, yards, meters, data miles, and nautical miles, as well as angular + * values to/from degrees and radians. To convert a value X in units of U to units of + * V, use X * Convert.U_TO_V. + */ +public class Convert +{ + public static final double FEET_TO_METERS = 0.3048; + public static final double DM_TO_METERS = 1828.8; + public static final double NM_TO_METERS = 1852.0; + public static final double MILES_TO_METERS = 1609.344; + public static final double YARDS_TO_METERS = 0.9144; // 3 * FEET_TO_METERS + + public static final double METERS_TO_FEET = 1.0 / FEET_TO_METERS; + public static final double DM_TO_FEET = 6000.0; + public static final double NM_TO_FEET = NM_TO_METERS * METERS_TO_FEET; + public static final double MILES_TO_FEET = 5280.0; + public static final double YARDS_TO_FEET = 3.0; + + public static final double METERS_TO_DM = 1.0 / DM_TO_METERS; + public static final double FEET_TO_DM = FEET_TO_METERS * METERS_TO_DM; + public static final double NM_TO_DM = NM_TO_METERS * METERS_TO_DM; + public static final double MILES_TO_DM = MILES_TO_METERS * METERS_TO_DM; + public static final double YARDS_TO_DM = YARDS_TO_METERS * METERS_TO_DM; + + public static final double METERS_TO_NM = 1.0 / NM_TO_METERS; + public static final double FEET_TO_NM = FEET_TO_METERS * METERS_TO_NM; + public static final double DM_TO_NM = DM_TO_METERS * METERS_TO_NM; + public static final double MILES_TO_NM = MILES_TO_METERS * METERS_TO_NM; + public static final double YARDS_TO_NM = YARDS_TO_METERS * NM_TO_METERS; + + public static final double METERS_TO_MILES = 1.0 / MILES_TO_METERS; + public static final double FEET_TO_MILES = FEET_TO_METERS * METERS_TO_MILES; + public static final double DM_TO_MILES = DM_TO_METERS * METERS_TO_MILES; + public static final double NM_TO_MILES = NM_TO_METERS * METERS_TO_MILES; + public static final double YARDS_TO_MILES = YARDS_TO_METERS * METERS_TO_MILES; + + public static final double METERS_TO_YARDS = 1.0 / YARDS_TO_METERS; + public static final double FEET_TO_YARDS = 1.0 / 3.0; + public static final double DM_TO_YARDS = 2000.0; + public static final double NM_TO_YARDS = NM_TO_METERS * METERS_TO_YARDS; + public static final double MILES_TO_YARDS = 1760.0; + + public static final double RAD_TO_DEG = 180.0 / Math.PI; + public static final double DEG_TO_RAD = Math.PI / 180.0; + + public static final double SECS_TO_MSECS = 1000.0; + public static final double MSECS_TO_SECS = 1.0 / SECS_TO_MSECS; + + public static final int MINS_TO_SECS = 60; + public static final int HOURS_TO_MINS = 60; + public static final int HOURS_TO_SECS = HOURS_TO_MINS * MINS_TO_SECS; + + public static final double SECS_TO_MINS = 1 / MINS_TO_SECS; + public static final double MINS_TO_HOURS = 1 / HOURS_TO_MINS; + public static final double SECS_TO_HOURS = 1 / HOURS_TO_SECS; + + /** + * Constructor + */ + private Convert() + { + } + + /** + * Converts an angle to a bearing + */ + public static double angleToBearing(double aAngle) + { + double bearing; + + bearing = 180 - (aAngle + 90); + if (bearing < 0) + bearing += 360; + + return bearing; + } + + /** + * Converts a bearing to an angle + */ + public static double bearingToAngle(double aBearing) + { + double angle; + + angle = 180 - (aBearing + 90); + if (angle < 0) + angle += 360; + + return angle; + } + +} diff --git a/src/glum/coord/CoordUtil.java b/src/glum/coord/CoordUtil.java new file mode 100644 index 0000000..c0fa1a6 --- /dev/null +++ b/src/glum/coord/CoordUtil.java @@ -0,0 +1,197 @@ +package glum.coord; + + +/** Provides a few useful functions on coordinates, such as converting +* to a user-presentable string. +*/ +public class CoordUtil +{ + /** Convert a Lat/Lon to a pair of DEG:MM:SS H strings. H is the + * hemisphere, N or S for lat, E or W for lon. The default separator + * string LL_SEP is used. + */ + public static String LatLonToString (LatLon ll) + { + return ll == null ? "" : LatLonToString (ll, LL_SEP); + } + + /** Same as the other LatLonToString, excepts this one uses the + * given sep string to separate the Lat and Lon. + */ + public static String LatLonToString (LatLon ll, String sep) + { + return ll == null ? "" : + LatToString (ll.lat) + LL_SEP + LonToString (ll.lon); + } + + /** Converts the given lat to DD:MM:SS H. */ + + public static String LatToString (double lat) + { + return LatToString (lat, true); + } + + /** Converts the given lat to DD:MM:SS H if + * include_seconds is true. If it's false, then the + * :SS part is left off. + */ + + public static String LatToString (double lat, boolean include_seconds) + { + DMS dms = new DMS (lat); + StringBuffer s = new StringBuffer(); + + if ( dms.degrees < 10 ) + s.append ("0"); + s.append (dms.degrees); + s.append (":"); + if ( dms.minutes < 10 ) + s.append ("0"); + s.append (dms.minutes); + if ( include_seconds ) + { + s.append (":"); + if ( dms.seconds < 10 ) + s.append ("0"); + s.append (dms.seconds); + } + s.append (lat >= 0 ? " N" : " S"); + return s.toString(); + } + + /** Similar to LatToString except that the degrees + * part is DDD instead of DD. */ + + public static String LonToString (double lon) + { + return LonToString (lon, true); + } + + /** Similar to LatToString except that the degrees + * part is DDD instead of DD. */ + + public static String LonToString (double lon, boolean include_seconds) + { + DMS dms = new DMS (lon); + StringBuffer s = new StringBuffer(); + + if ( dms.degrees < 100 ) + s.append ("0"); + if ( dms.degrees < 10 ) + s.append ("0"); + s.append (dms.degrees); + s.append (":"); + if ( dms.minutes < 10 ) + s.append ("0"); + s.append (dms.minutes); + if ( include_seconds ) + { + s.append (":"); + if ( dms.seconds < 10 ) + s.append ("0"); + s.append (dms.seconds); + } + s.append (lon >= 0 ? " E" : " W"); + return s.toString(); + } + + /** Converts dmsh_string to a double value. + * The string format should match the output of the + * LatToString formats, including hemisphere. + * If a hemisphere character is not part of the string, the + * returned value will be non-negative. + */ + public static double StringToLat (String dmsh_string) + { + if ( dmsh_string == null || dmsh_string.length() == 0 ) + return 0.0; + + int dms [] = StringToDMS (dmsh_string); + + if ( dms.length == 3 ) + return new Degrees (dms[0], dms[1], dms[2]).degrees; + else return 0.0; + } + + /** {@see StringToLat} */ + + public static double StringToLon (String dmsh_string) + { + // Because we aren't doing any range or hemisphere error + // checking, a lon value is identical to a lat value. + return StringToLat (dmsh_string); + } + + /** Converts dmsh_string to a an array of + * 3 ints representing degrees, minutes, and seconds. + * if a hemisphere character is present (one of NSEW or + * nsew), and it represents a souther or western hemisphere, + * then the degrees value, in index 0 of the returned array, + * will be a non-positive number. + */ + public static int [] StringToDMS (String dmsh_string) + { + if ( dmsh_string == null || dmsh_string.length() == 0 ) + return null; + + char chars [] = dmsh_string.toCharArray(); + int dms [] = new int [ 3 ]; + + dms[0] = 0; + for ( int i = 0, j = 0; i < chars.length; i++ ) + { + char c = chars[i]; + + if ( c == ' ' || c == ' ' ) // Space or tab. + continue; + else if ( c >= '0' && c <= '9' && j < 3 ) + dms[j] = dms[j] * 10 + c - '0'; + else if ( c == ':' ) + { + j++; + dms[j] = 0; + } + else if ( c == 'S' || c == 's' || c == 'W' || c == 'w' ) + dms[0] = -dms[0]; + } + + return dms; + } + + public static class DMS + { + public DMS (double deg) + { + if ( deg < 0 ) deg = -deg; + degrees = (int) deg; + minutes = (int) (deg * 60) % 60; + seconds = (int) (deg * 3600) % 60; + } + public int degrees, minutes, seconds; + } + + public static class Degrees + { + public Degrees (int deg, int min, int sec) + { + degrees = Math.abs (deg) + + Math.abs(min) / 60.0 + + Math.abs(sec) / 3600.0; + if ( deg < 0 || min < 0 || sec < 0 ) + degrees = -degrees; + } + public Degrees (int deg, int min, int sec, char hemisphere) + { + this (deg, min, sec); + if ( hemisphere == 'N' || hemisphere == 'n' || + hemisphere == 'E' || hemisphere == 'e' ) + degrees = Math.abs (degrees); + else if ( hemisphere == 'S' || hemisphere == 's' || + hemisphere == 'W' || hemisphere == 'w' ) + degrees = -Math.abs (degrees); + } + public double degrees; + } + + public static String LL_SEP = " / "; +} diff --git a/src/glum/coord/Epsilon.java b/src/glum/coord/Epsilon.java new file mode 100644 index 0000000..3c4012a --- /dev/null +++ b/src/glum/coord/Epsilon.java @@ -0,0 +1,43 @@ +package glum.coord; + +/** Determines if two numbers are close, usually as a way to say +* that they are equal. Close is defined to mean that their difference +* is less than some small number, which is either supplied by the caller +* or is EPSILON. +*

For longitude near the equator, a difference of EPSILON is about +* 3.65 feet (where the earth's circumference is about 21913.3 DM, or +* about 60.87 DM per degree longitude). For DataMile measurements, it's +* about 0.72 inches. +*/ + +public class Epsilon +{ + /** The measure of closeness; set to 0.00001. */ + public static final double EPSILON = 0.00001; + + public static boolean close (float a, float b) + { + float diff = a - b; + return diff < EPSILON && diff > -EPSILON; + } + + public static boolean close (float a, float b, float epsilon) + { + float diff = a - b; + return diff < epsilon && diff > -epsilon; + } + + public static boolean close (double a, double b) + { + double diff = a - b; + return diff < EPSILON && diff > -EPSILON; + } + + public static boolean close (double a, double b, float epsilon) + { + double diff = a - b; + return diff < EPSILON && diff > -EPSILON; + } + + private Epsilon () { } +} diff --git a/src/glum/coord/GeoUtil.java b/src/glum/coord/GeoUtil.java new file mode 100644 index 0000000..4f76522 --- /dev/null +++ b/src/glum/coord/GeoUtil.java @@ -0,0 +1,97 @@ +package glum.coord; + +/** + * Contains a collection of utility methods to perform linear algebra using the objects from this package. + */ +public class GeoUtil +{ + /** + * realSqr returns aNum*aNum + */ + public static double realSqr(double aNum) + { + return aNum * aNum; + } + + /** + * computeDotProduct - Returns the dot product of vector1 and vector2 + */ + public static double computeDotProduct(Point3D vector1, Point3D vector2) + { + return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z; + } + + /** + * computeDistance - Returns the distance between pt1 and pt2 + */ + public static double computeDistance(Point3D pt1, Point3D pt2) + { + return Math.sqrt(realSqr(pt1.x - pt2.x) + realSqr(pt1.y - pt2.y) + realSqr(pt1.z - pt2.z)); + } + + /** + * computeDistanceSquare - Returns the squared distance between pt1 and pt2 + */ + public static double computeDistanceSquare(Point3D pt1, Point3D pt2) + { + return realSqr(pt1.x - pt2.x) + realSqr(pt1.y - pt2.y) + realSqr(pt1.z - pt2.z); + } + + /** + * computeLength - Returns the magnitude of aVector + */ + public static double computeLength(Point3D aVector) + { + return Math.sqrt(realSqr(aVector.x) + realSqr(aVector.y) + realSqr(aVector.z)); + } + + /** + * computeNormal - Returns the R.H.R normal defined by the 3 points + */ + public static void computeNormal(Point3D pt1, Point3D pt2, Point3D pt3, Point3D aNormal) + { + Point3D vector1, vector2; + + vector1 = new Point3D(); + vector2 = new Point3D(); + computeVector(pt1, pt3, vector1); + computeVector(pt3, pt2, vector2); + + // ! Not sure why I have to negate all the values; Need to refer to linear alg. +//! aNormal.x = vector1.y*vector2.z - vector1.z*vector2.y; +//! aNormal.y = vector1.z*vector2.x - vector1.x*vector2.z; +//! aNormal.z = vector1.x*vector2.y - vector1.y*vector2.x; + aNormal.x = -(vector1.y * vector2.z - vector1.z * vector2.y); + aNormal.y = -(vector1.z * vector2.x - vector1.x * vector2.z); + aNormal.z = -(vector1.x * vector2.y - vector1.y * vector2.x); + + // Normalize the vector + normalizeVector(aNormal); + } + + /** + * computeVector - Returns the vector defined by the 2 points + */ + public static void computeVector(Point3D pt1, Point3D pt2, Point3D aVector) + { + aVector.x = pt2.x - pt1.x; + aVector.y = pt2.y - pt1.y; + aVector.z = pt2.z - pt1.z; + } + + /** + * normalizeVector - Normalizes aVector so that its length is 1 + */ + public static void normalizeVector(Point3D aVector) + { + double length; + + length = computeLength(aVector); + + // Normalize the vector + aVector.x = aVector.x / length; + aVector.y = aVector.y / length; + aVector.z = aVector.z / length; + } + +} diff --git a/src/glum/coord/LatLon.java b/src/glum/coord/LatLon.java new file mode 100644 index 0000000..c9a954c --- /dev/null +++ b/src/glum/coord/LatLon.java @@ -0,0 +1,111 @@ +package glum.coord; + +/** Simple class for Lat/Lon values. */ +public class LatLon +{ + public double lat; + public double lon; + + public LatLon() + { + } + + public LatLon(LatLon latlon) + { + if (latlon != null) + { + lat = latlon.lat; + lon = latlon.lon; + } + } + + public LatLon(double lat, double lon) + { + this.lat = lat; + this.lon = lon; + } + + public LatLon(String lat_string, String lon_string) + { + set(lat_string, lon_string); + } + + public void set(double lat, double lon) + { + this.lat = lat; + this.lon = lon; + } + + public void set(LatLon latlon) + { + if (latlon != null) + { + lat = latlon.lat; + lon = latlon.lon; + } + } + + public void set(String lat_string, String lon_string) + { + lat = CoordUtil.StringToLat(lat_string); + lon = CoordUtil.StringToLon(lon_string); + } + + public void normalize() + { + if (lat > 90) + lat = 90; + else if (lat < -90) + lat = -90; + + if (lon > 180) + lon -= 360; + else if (lon < -180) + lon += 360; + } + + /** + * Tests to see if the given object is the same lat/lon as this position. + * "Same" really means "very, very close," as defined by {@link Epsilon}. + * + * @return True if obj is a LatLon and is very close to our lat/lon position. + * False otherwise. + */ + @Override + public boolean equals(Object obj) + { + return (obj instanceof LatLon) && Epsilon.close(lat, ((LatLon)obj).lat) && Epsilon.close(lon, ((LatLon)obj).lon); + } + + @Override + public String toString() + { + return CoordUtil.LatLonToString(this); + } + + /** + * Returns the change in latitude + */ + static public double computeDeltaLat(double lat1, double lat2) + { + return lat2 - lat1; + } + + /** + * Returns the change in longitude + */ + static public double computeDeltaLon(double lon1, double lon2) + { + double dLon; + + dLon = lon2 - lon1; + if (Math.abs(dLon) < 180) + return dLon; + + if (dLon > 180) + return dLon - 360; + else + return dLon + 360; + } + +} diff --git a/src/glum/coord/Point2D.java b/src/glum/coord/Point2D.java new file mode 100644 index 0000000..7bff1fe --- /dev/null +++ b/src/glum/coord/Point2D.java @@ -0,0 +1,37 @@ +package glum.coord; + +public class Point2D +{ + public double x; + public double y; + + public Point2D () { } + + public Point2D (Point2D pt) + { if ( pt != null ) { x = pt.x; y = pt.y; } } + + public Point2D (double x, double y) + { this.x = x; this.y = y; } + + public void set (double x, double y) + { this.x = x; this.y = y; } + + public void set (Point2D pt) + { if ( pt != null ) { x = pt.x; y = pt.y; } } + + public double distance (Point2D aPt) + { + if (aPt == null) + return 0; + + return Math.sqrt((aPt.x - x)*(aPt.x - x) + (aPt.y - y)*(aPt.y - y)); + } + + @Override + public boolean equals (Object obj) + { + return (obj instanceof Point2D) && + Epsilon.close (x, ((Point2D) obj).x) && + Epsilon.close (y, ((Point2D) obj).y); + } +} diff --git a/src/glum/coord/Point2Di.java b/src/glum/coord/Point2Di.java new file mode 100644 index 0000000..f7f09d5 --- /dev/null +++ b/src/glum/coord/Point2Di.java @@ -0,0 +1,27 @@ +package glum.coord; + +public class Point2Di +{ + public int x; + public int y; + + public Point2Di () { } + + public Point2Di (Point2Di pt) + { if ( pt != null ) { x = pt.x; y = pt.y; } } + + public Point2Di (int x, int y) { this.x = x; this.y = y; } + + public void set (int x, int y) { this.x = x; this.y = y; } + + public void set (Point2Di pt) + { if ( pt != null ) { x = pt.x; y = pt.y; } } + + @Override + public boolean equals (Object obj) + { + return (obj instanceof Point2Di) && + x == ((Point2Di) obj).x && + y == ((Point2Di) obj).y; + } +} diff --git a/src/glum/coord/Point3D.java b/src/glum/coord/Point3D.java new file mode 100644 index 0000000..7fabc60 --- /dev/null +++ b/src/glum/coord/Point3D.java @@ -0,0 +1,42 @@ +package glum.coord; + +/** A class for representing any 3-Dimensional vector, which could be +* a position, a velocity, or a rotation. No information about units +* is assumed or implied. +*/ + +public class Point3D +{ + public double x; + public double y; + public double z; + + public Point3D () { } + + public Point3D (Point3D pt) + { if ( pt != null ) { x = pt.x; y = pt.y; z = pt.z; } } + + public Point3D (double x, double y, double z) + { this.x = x; this.y = y; this.z = z; } + + public void set (double x, double y, double z) + { this.x = x; this.y = y; this.z = z; } + + public void set (Point3D pt) + { if ( pt != null ) { x = pt.x; y = pt.y; z = pt.z; } } + + @Override + public boolean equals (Object obj) + { + return (obj instanceof Point3D) && + Epsilon.close (x, ((Point3D) obj).x) && + Epsilon.close (y, ((Point3D) obj).y) && + Epsilon.close (z, ((Point3D) obj).z); + } + + @Override + public String toString() + { + return new String("(" + x + ", " + y + ", " + z + ")"); + } +} diff --git a/src/glum/coord/RngBrg.java b/src/glum/coord/RngBrg.java new file mode 100644 index 0000000..cc79fe2 --- /dev/null +++ b/src/glum/coord/RngBrg.java @@ -0,0 +1,47 @@ +package glum.coord; + +public class RngBrg +{ + public double rng; + public double brg; + + public RngBrg() { } + + public RngBrg(RngBrg pt) + { + if ( pt != null ) + { + rng = pt.rng; + brg = pt.brg; + } + } + + public RngBrg (double rng, double brg) + { + this.rng = rng; + this.brg = brg; + } + + public void set (double rng, double brg) + { + this.rng = rng; + this.brg = brg; + } + + public void set (RngBrg pt) + { + if ( pt != null ) + { + rng = pt.rng; + brg = pt.brg; + } + } + + @Override + public boolean equals (Object obj) + { + return (obj instanceof RngBrg) && + Epsilon.close (rng, ((RngBrg) obj).rng) && + Epsilon.close (brg, ((RngBrg) obj).brg); + } +} diff --git a/src/glum/coord/UV.java b/src/glum/coord/UV.java new file mode 100644 index 0000000..8c7150d --- /dev/null +++ b/src/glum/coord/UV.java @@ -0,0 +1,26 @@ +package glum.coord; + +public class UV extends Point2D +{ + public UV () + { + x = 0; + y = 0; + } + + public UV (Point2D pt) + { + if (pt != null) + { + x = pt.x; + y = pt.y; + } + } + + public UV (double x, double y) + { + this.x = x; + this.y = y; + } + +} diff --git a/src/glum/database/QueryItem.java b/src/glum/database/QueryItem.java new file mode 100644 index 0000000..6a8aa95 --- /dev/null +++ b/src/glum/database/QueryItem.java @@ -0,0 +1,15 @@ +package glum.database; + +public abstract interface QueryItem> +{ + /** + * Returns the corresponding value associated with aEnum + */ + public Object getValue(G1 aEnum); + + /** + * Sets in the aObj as the corresponding value to aEnum + */ + public void setValue(G1 aEnum, Object aObj); + +} diff --git a/src/glum/database/QueryItemComparator.java b/src/glum/database/QueryItemComparator.java new file mode 100644 index 0000000..3365316 --- /dev/null +++ b/src/glum/database/QueryItemComparator.java @@ -0,0 +1,62 @@ +package glum.database; + +import java.util.Comparator; + +public class QueryItemComparator, G2 extends Enum> implements Comparator +{ + private G2 sortKey; + + public QueryItemComparator(G2 aSortKey) + { + sortKey = aSortKey; + } + + @SuppressWarnings("unchecked") + @Override + public int compare(G1 item1, G1 item2) + { + Comparable value1, value2; + + value1 = (Comparable)item1.getValue(sortKey); + value2 = (Comparable)item2.getValue(sortKey); + + if (value1 == null && value2 == null) + return 0; + + if (value1 == null) + return -1; + + if (value2 == null) + return 1; + + return value1.compareTo(value2); + } + + /** + * Utility method to create a QueryItemComparator by specifying the class and sort Enum. + *

+ * This logic is here due to Java's horrible implementation off generics. + */ + public static , G4 extends Enum> Comparator spawn(Class aClass, G4 aEnum) + { + QueryItemComparator retComparator; + + retComparator = new QueryItemComparator(aEnum); + return retComparator; + } + + /** + * Utility method to create a QueryItemComparator by specifying just the Enum. Note this method can not be used in a + * argument to another method; instead use: {@link #spawn(Class, Enum)} + *

+ * This logic is here due to Java's horrible implementation off generics. + */ + public static , G4 extends Enum> Comparator spawn(G4 aEnum) + { + QueryItemComparator retComparator; + + retComparator = new QueryItemComparator(aEnum); + return retComparator; + } + +} diff --git a/src/glum/filter/EnumFilter.java b/src/glum/filter/EnumFilter.java new file mode 100644 index 0000000..1b66302 --- /dev/null +++ b/src/glum/filter/EnumFilter.java @@ -0,0 +1,120 @@ +package glum.filter; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +public abstract class EnumFilter> implements ZioRaw, Filter +{ + // Static config vars + private Map> fullMap; + + // State vars + private Set> validSet; + private boolean isEnabled; + + public EnumFilter(Class> enumClass) + { + fullMap = Maps.newLinkedHashMap(); + for (Enum aEnum : enumClass.getEnumConstants()) + fullMap.put(aEnum.ordinal(), aEnum); + + validSet = Sets.newLinkedHashSet(); + isEnabled = false; + } + + /** + * Returns true if the filter is active. + */ + public boolean getIsEnabled() + { + return isEnabled; + } + + /** + * Returns the list of valid enums for this filter. + */ + public List> getSelectedItems() + { + return Lists.newArrayList(validSet); + } + + /** + * Sets this filter to match aFilter + */ + public void set(EnumFilter aFilter) + { + validSet = Sets.newLinkedHashSet(aFilter.validSet); + isEnabled = aFilter.getIsEnabled(); + } + + /** + * Sets whether the filter is active. + */ + public void setIsEnabled(boolean aBool) + { + isEnabled = aBool; + } + + /** + * Sets the list of valid enums for this filter. + */ + public void setSetSelectedItems(List> selectedItems) + { + validSet.clear(); + validSet.addAll(selectedItems); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + int numItems; + + aStream.readVersion(0); + + // Read the payload + isEnabled = aStream.readBool(); + + validSet.clear(); + numItems = aStream.readInt(); + for (int c1 = 0; c1 < numItems; c1++) + validSet.add(fullMap.get(aStream.readInt())); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + int numItems; + + aStream.writeVersion(0); + + aStream.writeBool(isEnabled); + + numItems = validSet.size(); + aStream.writeInt(numItems); + for (Enum aEnum : validSet) + aStream.writeInt(aEnum.ordinal()); + } + + /** + * Utility method that returns whether aValue is within the constraints + * specified by this filter. + */ + protected boolean testIsValid(G2 aEnum) + { + if (isEnabled == false) + return true; + + return validSet.contains(aEnum); + } + +} diff --git a/src/glum/filter/Filter.java b/src/glum/filter/Filter.java new file mode 100644 index 0000000..ba150f2 --- /dev/null +++ b/src/glum/filter/Filter.java @@ -0,0 +1,12 @@ +package glum.filter; + +import glum.zio.raw.ZioRaw; + +public interface Filter extends ZioRaw +{ + /** + * Method that returns true if aItem passes this filter + */ + public boolean isValid(G1 aItem); + +} diff --git a/src/glum/filter/FilterUtil.java b/src/glum/filter/FilterUtil.java new file mode 100644 index 0000000..f5b5bb9 --- /dev/null +++ b/src/glum/filter/FilterUtil.java @@ -0,0 +1,100 @@ +package glum.filter; + +import java.util.List; + +import javax.swing.JCheckBox; + +import com.google.common.collect.Lists; + +import glum.gui.component.GList; +import glum.gui.component.GNumberField; + +public class FilterUtil +{ + /** + * Utility method to return a sublist of itemList based on aFilter. + */ + public static List applyFilter(List itemList, Filter aFilter) + { + List retList; + + retList = Lists.newArrayList(); + for (G1 aItem : itemList) + { + if (aFilter.isValid(aItem) == true) + retList.add(aItem); + } + + return retList; + } + + /** + * Utility method to synchronize the specified filter with the associated GUI controls. + */ + public static void getEnumFilter(EnumFilter> aFilter, JCheckBox mainCB, GList> mainList) + { + aFilter.setIsEnabled(mainCB.isSelected()); + aFilter.setSetSelectedItems(mainList.getSelectedItems()); + } + + /** + * Utility method to synchronize the associated GUI controls with the specified filter. + */ + public static void setEnumFilter(EnumFilter> aFilter, JCheckBox mainCB, GList> mainList) + { + mainCB.setSelected(aFilter.getIsEnabled()); + mainList.setSelectedItems(aFilter.getSelectedItems()); + } + + /** + * Utility method to synchronize the specified filter with the associated GUI controls. + */ + public static void getRangeFilter(RangeFilter aFilter, JCheckBox mainCB, JCheckBox minCB, JCheckBox maxCB, GNumberField minNF, GNumberField maxNF) + { + aFilter.setIsEnabled(mainCB.isSelected()); + aFilter.setUseMin(minCB.isSelected()); + aFilter.setUseMax(maxCB.isSelected()); + aFilter.setMinValue(minNF.getValue()); + aFilter.setMaxValue(maxNF.getValue()); + } + + /** + * Utility method to synchronize the associated GUI controls with the specified filter. + */ + public static void setRangeGui(RangeFilter aFilter, JCheckBox mainCB, JCheckBox minCB, JCheckBox maxCB, GNumberField minNF, GNumberField maxNF) + { + mainCB.setSelected(aFilter.getIsEnabled()); + minCB.setSelected(aFilter.getUseMin()); + maxCB.setSelected(aFilter.getUseMax()); + minNF.setValue(aFilter.getMinValue()); + maxNF.setValue(aFilter.getMaxValue()); + } + + /** + * Utility method to keep the various GUI components associated with an EnumFilter synchronized. + * The mainList will be enabled/disabled based on the selection state of mainCB. + */ + public static void syncEnumGui(JCheckBox mainCB, GList> mainList) + { + boolean isEnabled; + + isEnabled = mainCB.isSelected(); + mainList.setEnabled(isEnabled); + } + + /** + * Utility method to keep the various GUI components associated with an RangeFilter synchronized. + * Gui components will be enabled/disabled based on the various check boxes. + */ + public static void syncRangeGui(JCheckBox mainCB, JCheckBox minCB, JCheckBox maxCB, GNumberField minNF, GNumberField maxNF) + { + boolean isEnabled; + + isEnabled = mainCB.isSelected(); + minCB.setEnabled(isEnabled); + maxCB.setEnabled(isEnabled); + minNF.setEnabled(isEnabled & minCB.isSelected()); + maxNF.setEnabled(isEnabled & maxCB.isSelected()); + } + +} diff --git a/src/glum/filter/NullFilter.java b/src/glum/filter/NullFilter.java new file mode 100644 index 0000000..de45475 --- /dev/null +++ b/src/glum/filter/NullFilter.java @@ -0,0 +1,32 @@ +package glum.filter; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; + +/** + * A Filter which does not filter anything. Thus the method isValid() always returns true. + */ +public class NullFilter implements Filter +{ + + @Override + public boolean isValid(G1 aItem) + { + return true; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + ; // Nothing to do + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + ; // Nothing to do + } + +} diff --git a/src/glum/filter/RangeFilter.java b/src/glum/filter/RangeFilter.java new file mode 100644 index 0000000..ec78809 --- /dev/null +++ b/src/glum/filter/RangeFilter.java @@ -0,0 +1,122 @@ +package glum.filter; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.io.IOException; + +/** + * Abstract filter which is used to filter a single value between the specified min/max ranges. The only code to write + * is the isValid() method and to call the appropriate Constructor. In the isValid() method, you should delegate filter + * logic to the method testIsValid() with the quantity of interest, and return the result from the method call. + */ +public abstract class RangeFilter implements ZioRaw, Filter +{ + private boolean isEnabled; + private boolean useMin, useMax; + private double minValue, maxValue; + + /** + * @param aBinCode + * Unique identifier used during serialization. The value specified here should not collide with any other + * codes for which there is serialization. + */ + public RangeFilter() + { + isEnabled = false; + useMin = false; + useMax = false; + minValue = 0; + maxValue = 0; + } + + /** + * Accessor methods + */ + // @formatter:off + public boolean getIsEnabled() { return isEnabled; } + public boolean getUseMin() { return useMin; } + public boolean getUseMax() { return useMax; } + public double getMinValue() { return minValue; } + public double getMaxValue() { return maxValue; } + + public void setIsEnabled(boolean aBool) { isEnabled = aBool; } + public void setUseMin(boolean aBool) { useMin = aBool; } + public void setUseMax(boolean aBool) { useMax = aBool; } + public void setMinValue(double aValue) { minValue = aValue; } + public void setMaxValue(double aValue) { maxValue = aValue; } + // @formatter:on + + /** + * Sets this filter to match aFilter + */ + public void set(RangeFilter aFilter) + { + isEnabled = aFilter.getIsEnabled(); + useMin = aFilter.getUseMin(); + useMax = aFilter.getUseMax(); + minValue = aFilter.getMinValue(); + maxValue = aFilter.getMaxValue(); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + byte bSwitch; + + aStream.readVersion(0); + + bSwitch = aStream.readByte(); + isEnabled = (bSwitch & 0x1) != 0; + useMin = (bSwitch & 0x2) != 0; + useMax = (bSwitch & 0x4) != 0; + +// isEnabled = aStream.readBoolean(); +// useMin = aStream.readBoolean(); +// useMax = aStream.readBoolean(); + minValue = aStream.readDouble(); + maxValue = aStream.readDouble(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + byte bSwitch; + + aStream.writeVersion(0); + + bSwitch = 0; + if (isEnabled == true) + bSwitch |= 0x1; + if (useMin == true) + bSwitch |= 0x2; + if (useMax == true) + bSwitch |= 0x4; + aStream.writeByte(bSwitch); + +// aStream.writeBoolean(isEnabled); +// aStream.writeBoolean(useMin); +// aStream.writeBoolean(useMax); + aStream.writeDouble(minValue); + aStream.writeDouble(maxValue); + } + + /** + * Utility method that returns whether aValue is within the constraints specified by this filter. + */ + protected boolean testIsValid(double aValue) + { + if (isEnabled == false) + return true; + + if (useMin == true && aValue < minValue) + return false; + + if (useMax == true && aValue > maxValue) + return false; + + return true; + } + +} diff --git a/src/glum/gui/FocusUtil.java b/src/glum/gui/FocusUtil.java new file mode 100644 index 0000000..e37c1d5 --- /dev/null +++ b/src/glum/gui/FocusUtil.java @@ -0,0 +1,66 @@ +package glum.gui; + +import javax.swing.*; + +public class FocusUtil +{ + /** + * Adds a keyboard shortcut to bind aKeyStroke for the specified action. + * This keyboard shortcut will be executed whenever a child component of aComp has the focus. + */ + public static void addAncestorKeyBinding(JComponent aComp, String aKeyStroke, Action aAction) + { + addAncestorKeyBinding(aComp, convertStringToKeyStroke(aKeyStroke), aAction); + } + + public static void addAncestorKeyBinding(JComponent aComp, KeyStroke aKeyStroke, Action aAction) + { + aComp.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(aKeyStroke, aAction); + aComp.getActionMap().put(aAction, aAction); + } + + /** + * Adds a keyboard shortcut to bind aKeyStroke for the specified action. + * This keyboard shortcut will be executed aComp has the focus. + */ + public static void addFocusKeyBinding(JComponent aComp, String aKeyStroke, Action aAction) + { + addFocusKeyBinding(aComp, convertStringToKeyStroke(aKeyStroke), aAction); + } + + public static void addFocusKeyBinding(JComponent aComp, KeyStroke aKeyStroke, Action aAction) + { + aComp.getInputMap(JComponent.WHEN_FOCUSED).put(aKeyStroke, aAction); + aComp.getActionMap().put(aAction, aAction); + } + + /** + * Adds a keyboard shortcut to bind aKeyStroke for the specified action. + * This keyboard shortcut will be executed whenever the parent Window has the focus. + */ + public static void addWindowKeyBinding(JComponent aComp, String aKeyStroke, Action aAction) + { + addWindowKeyBinding(aComp, convertStringToKeyStroke(aKeyStroke), aAction); + } + + public static void addWindowKeyBinding(JComponent aComp, KeyStroke aKeyStroke, Action aAction) + { + aComp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(aKeyStroke, aAction); + aComp.getActionMap().put(aAction, aAction); + } + + /** + * Converts a String to a valid KeyStroke. + */ + public static KeyStroke convertStringToKeyStroke(String aStr) + { + KeyStroke aKeyStroke; + + aKeyStroke = KeyStroke.getKeyStroke(aStr); + if (aKeyStroke == null) + throw new RuntimeException("Failed to convert: [" + aStr + "] to a keystroke."); + + return aKeyStroke; + } + +} diff --git a/src/glum/gui/GuiUtil.java b/src/glum/gui/GuiUtil.java new file mode 100644 index 0000000..6158c85 --- /dev/null +++ b/src/glum/gui/GuiUtil.java @@ -0,0 +1,688 @@ +package glum.gui; + +import glum.gui.icon.IconUtil; +import glum.reflect.Function; +import glum.reflect.FunctionRunnable; + +import java.awt.*; +import java.awt.event.ActionListener; +import java.util.Collection; +import javax.swing.*; +import javax.swing.border.BevelBorder; +import javax.swing.event.ChangeListener; + +public class GuiUtil +{ + /** + * Method to examine the labels and returns the size of the largest button. + */ + public static Dimension computePreferredJButtonSize(String... labels) + { + Dimension tmpDim, maxDim; + JButton tmpB; + + maxDim = null; + tmpB = new JButton(""); + + // Find the label that requires the largest dimension + for (String aStr : labels) + { + if (aStr == null) + aStr = ""; + + tmpB.setText(aStr); + tmpDim = tmpB.getPreferredSize(); + + if (maxDim == null || maxDim.getWidth() < tmpDim.getWidth()) + maxDim = tmpDim; + } + + return maxDim; + } + + /** + * Creates a JButton with the specified settings + */ + public static JButton createJButton(String aTitle, ActionListener aActionListener) + { + JButton tmpB; + + tmpB = new JButton(aTitle); + tmpB.addActionListener(aActionListener); + return tmpB; + } + + public static JButton createJButton(String aTitle, ActionListener aActionListener, Font aFont) + { + JButton tmpB; + + tmpB = new JButton(aTitle); + tmpB.addActionListener(aActionListener); + if (aFont != null) + tmpB.setFont(aFont); + + return tmpB; + } + + public static JButton createJButton(String aTitle, ActionListener aActionListener, Dimension aDimension) + { + JButton tmpB; + + tmpB = new JButton(aTitle); + tmpB.addActionListener(aActionListener); + + // Force a dimension + if (aDimension != null) + { + tmpB.setMinimumSize(aDimension); + tmpB.setMaximumSize(aDimension); + tmpB.setPreferredSize(aDimension); + } + + return tmpB; + } + + public static JButton createJButton(Icon aIcon, ActionListener aActionListener) + { + return createJButton(aIcon, aActionListener, null); + } + + public static JButton createJButton(Icon aIcon, ActionListener aActionListener, String aToolTip) + { + JButton tmpB; + + tmpB = new JButton(aIcon); + tmpB.addActionListener(aActionListener); + + if (aToolTip != null) + tmpB.setToolTipText(aToolTip); + + return tmpB; + } + + /** + * Creates the JButton with the specified resource icon. + */ + public static JButton createJButtonViaResource(ActionListener aHandler, String aResourcePath) + { + return createJButtonViaResource(aHandler, aResourcePath, null); + } + + public static JButton createJButtonViaResource(ActionListener aHandler, String aResourcePath, String aToolTip) + { + JButton tmpB; + + tmpB = new JButton(IconUtil.loadIcon(aResourcePath)); + tmpB.addActionListener(aHandler); + + if (aToolTip != null) + tmpB.setToolTipText(aToolTip); + + return tmpB; + } + + /** + * Creates a JCheckBox with the specified settings + */ + public static JCheckBox createJCheckBox(String aTitle, ActionListener aActionListener) + { + return createJCheckBox(aTitle, aActionListener, null); + } + + public static JCheckBox createJCheckBox(String aTitle, ActionListener aActionListener, Font aFont) + { + JCheckBox tmpCB; + + tmpCB = new JCheckBox(aTitle); + tmpCB.addActionListener(aActionListener); + if (aFont != null) + tmpCB.setFont(aFont); + + return tmpCB; + } + + /** + * Creates a JComboBox with the specified settings + */ + public static JComboBox createJComboBox(ActionListener aListener, Font aFont, Object... itemArr) + { + JComboBox tmpBox; + + tmpBox = new JComboBox(); + for (Object aItem : itemArr) + tmpBox.addItem(aItem); + + if (aFont != null) + tmpBox.setFont(aFont); + + tmpBox.addActionListener(aListener); + return tmpBox; + } + +// /** +// * Creates a JComboBox with the specified settings +// */ +// public static JComboBox createJComboBox(ActionListener aListener, Object... itemArr) +// { +// return createJComboBox(aListener, null, itemArr); +// } + + /** + * Creates a JLabel with the specified settings + */ + public static JLabel createJLabel(String aTitle, Font aFont) + { + return createJLabel(aTitle, JLabel.LEADING, aFont); + } + + public static JLabel createJLabel(String aTitle, int aAlignment, Font aFont) + { + JLabel tmpL; + + tmpL = new JLabel(aTitle, aAlignment); + + if (aFont != null) + tmpL.setFont(aFont); + + return tmpL; + } + + /** + * Creates the JRadioButton with the following attributes. + */ + public static JRadioButton createJRadioButton(String aLabel, ActionListener aListener) + { + return createJRadioButton(aLabel, aListener, null); + } + + public static JRadioButton createJRadioButton(String aLabel, ActionListener aListener, Font aFont) + { + JRadioButton tmpRB; + + tmpRB = new JRadioButton(aLabel); + tmpRB.addActionListener(aListener); + if (aFont != null) + tmpRB.setFont(aFont); + + return tmpRB; + } + + /** + * Utility method for creating a visual thin divider + * + * Typically added to MigLayout (or like manager) with: add(aComp, "growx,h 4!,span,wrap"); + */ + public static JPanel createDivider() + { + JPanel tmpPanel; + + tmpPanel = new JPanel(); + tmpPanel.setBorder(new BevelBorder(BevelBorder.RAISED)); + + return tmpPanel; + } + + /** + * Creates an uneditable JTextArea with no border, non-opaque, line wrap enabled and word wrap enabled. + */ + public static JTextArea createUneditableTextArea(int rows, int cols) + { + JTextArea tmpTA; + + tmpTA = new JTextArea("", rows, cols); + tmpTA.setEditable(false); + tmpTA.setOpaque(false); + tmpTA.setLineWrap(true); + tmpTA.setWrapStyleWord(true); + + return tmpTA; + } + + /** + * Creates an uneditable JTextPane configured with non-opaque and content type of text/html. + */ + public static JTextPane createUneditableTextPane() + { + JTextPane tmpTP; + + tmpTP = new JTextPane(); + tmpTP.setEditable(false); + tmpTP.setOpaque(false); + tmpTP.setContentType("text/html"); + + return tmpTP; + } + + /** + * Creates an uneditable JTextField + */ + public static JTextField createUneditableTextField(String aTitle) + { + JTextField tmpTF; + + tmpTF = new JTextField(aTitle); + tmpTF.setBorder(null); + tmpTF.setEditable(false); + tmpTF.setOpaque(false); + + return tmpTF; + } + + /** + * Utility method to link a set of radio buttons together + */ + public static void linkRadioButtons(JRadioButton... buttonArr) + { + ButtonGroup tmpGroup; + + tmpGroup = new ButtonGroup(); + for (JRadioButton aItem : buttonArr) + tmpGroup.add(aItem); + } + + /** + * Reads a boolean from a string with out throwing a exception + */ + public static boolean readBoolean(String aStr, boolean aVal) + { + if (aStr == null) + return aVal; + + // Special case for 1 char strings + if (aStr.length() == 1) + { + char aChar; + + aChar = aStr.charAt(0); + if (aChar == 'T' || aChar == 't' || aChar == '1') + return true; + + return false; + } + + try + { + return Boolean.valueOf(aStr).booleanValue(); + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Reads a double from a string with out throwing a exception. Note aStr can have an number of separators: comma + * chars + */ + public static double readDouble(String aStr, double aVal) + { + try + { + aStr = aStr.replace(",", ""); + return Double.parseDouble(aStr); + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Reads a float from a string with out throwing a exception. Note aStr can have an number of separators: comma chars + */ + public static float readFloat(String aStr, float aVal) + { + try + { + aStr = aStr.replace(",", ""); + return Float.parseFloat(aStr); + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Reads an int from a string without throwing a exception Note aStr can have an number of separators: comma chars + */ + public static int readInt(String aStr, int aVal) + { + try + { + aStr = aStr.replace(",", ""); + return Integer.parseInt(aStr); + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Reads a long from a string without throwing a exception Note aStr can have an number of separators: comma chars + */ + public static long readLong(String aStr, long aVal) + { + try + { + aStr = aStr.replace(",", ""); + return Long.parseLong(aStr); + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Reads an int (forced to fit within a range) from a string with out throwing a exception + */ + public static int readRangeInt(String aStr, int minVal, int maxVal, int aVal) + { + int aInt; + + try + { + aInt = Integer.parseInt(aStr); + if (aInt < minVal) + aInt = minVal; + else if (aInt > maxVal) + aInt = maxVal; + + return aInt; + } + catch(Exception e) + { + return aVal; + } + } + + /** + * Utility method to locate the RootPaneContainer for the specified Component + */ + public static RootPaneContainer getRootPaneContainer(Component aComponent) + { + Container aParent; + + // Check to see if the Component is an actual RootPaneContainer + if (aComponent instanceof RootPaneContainer) + return (RootPaneContainer)aComponent; + + // Attempt to locate the RootPaneContainer (through our stack) + aParent = aComponent.getParent(); + while (aParent != null && (aParent instanceof RootPaneContainer) == false) + aParent = aParent.getParent(); + + // Bail if we failed to find the RootPaneContainer + if (aParent instanceof RootPaneContainer == false) + throw new RuntimeException("No valid (grand)parent associated with GlassPane."); + + return (RootPaneContainer)aParent; + } + + /** + * Utility method to locate all of the subcomponents contained in aContainer which are an instance of searchClass + */ + public static void locateAllSubComponents(Container aContainer, Collection itemList, Class... searchClassArr) + { + for (Component aComponent : aContainer.getComponents()) + { + for (Class aClass : searchClassArr) + { + if (aClass.isInstance(aComponent) == true) + { + itemList.add(aComponent); + break; + } + } + + if (aComponent instanceof Container) + locateAllSubComponents((Container)aComponent, itemList, searchClassArr); + } + } + + /** + * Utility method to force a Component to act as modal while it is visible Source: + * http://stackoverflow.com/questions/804023/how-do-i-simulate-a-modal-dialog-from-within-an-applet + */ + public static void modalWhileVisible(Component aComponent) + { + // Bail if not called from the EventDispatchThread + if (SwingUtilities.isEventDispatchThread() == false) + throw new RuntimeException("Visibility for modal components must be changed via the Event thread."); + + synchronized(aComponent) + { + try + { + EventQueue theQueue = aComponent.getToolkit().getSystemEventQueue(); + while (aComponent.isVisible()) + { +//System.out.println("About to dispatch event... component.isVisible():" + aComponent.isVisible()); + AWTEvent event = theQueue.getNextEvent(); + Object source = event.getSource(); + if (event instanceof ActiveEvent) + { + ((ActiveEvent)event).dispatch(); + } + else if (source instanceof Component) + { + ((Component)source).dispatchEvent(event); + } + else if (source instanceof MenuComponent) + { + ((MenuComponent)source).dispatchEvent(event); + } + else + { + System.err.println("Unable to dispatch: " + event); + } + } + } + catch(InterruptedException ignored) + { + } + } + } + + /** + * Utility to call a specific method (methodName) with specific parameters (aParamArr) on aComp and on all of the + * child subcomponents. The method will only be called if the components are an instance of refMatchClass. + *

+ * This is useful so that a component and all of its children can be disabled, hidden, etc
+ * Example: GuiUtil.callMethod(myPanel, setEnabled, false); + *

+ * Be aware, this is rather expensive, so do not call in time critical applications. + */ + public static void callMethod(Component aComp, Class refMatchClass, String aMethodName, Object... aParamArr) + { + Class[] typeArr; + + // Construct the associated type array + typeArr = new Class[0]; + if (aParamArr.length > 0) + { + // Determine the types of the specified arguments + typeArr = new Class[aParamArr.length]; + for (int c1 = 0; c1 < typeArr.length; c1++) + { + typeArr[c1] = null; + if (aParamArr[c1] != null) + typeArr[c1] = aParamArr[c1].getClass(); + } + } + + // Call the helper version + callMethodHelper(aComp, refMatchClass, aMethodName, typeArr, aParamArr); + } + + /** + * Helper method to callMethod + */ + private static void callMethodHelper(Component aComp, Class refMatchClass, String aMethodName, Class[] aTypeArr, Object[] aParamArr) + { + Component[] subCompArr; + Function aFunction; + + // Locate and call the actual method + if (refMatchClass.isInstance(aComp) == true) + { + try + { + aFunction = new Function(aComp, aMethodName, aTypeArr); + aFunction.invoke(aParamArr); + } + catch(NoSuchMethodException aExp1) + { + throw new RuntimeException("Failed to locate valid function. Method:" + aMethodName, aExp1); + } + catch(Exception aExp2) + { + throw new RuntimeException("Failed to execute function. Method:" + aMethodName, aExp2); + } + } + + // Bail if we do not have subcomponents + if (aComp instanceof Container == false) + return; + + // Recurse down our children + subCompArr = ((Container)aComp).getComponents(); + for (Component aSubComp : subCompArr) + callMethodHelper(aSubComp, refMatchClass, aMethodName, aTypeArr, aParamArr); + } + + /** + * Utility method to set all subcomponents to the specified enabled mode. + */ + // TODO: Phase this method out, replace with callMethod() + public static void setEnabled(Component aComp, boolean aBool) + { + Component[] subCompArr; + + aComp.setEnabled(aBool); + if (aComp instanceof Container == false) + return; + + subCompArr = ((Container)aComp).getComponents(); + for (Component aSubComp : subCompArr) + GuiUtil.setEnabled(aSubComp, aBool); + } + + /** + * Utility method to set the enabled switch on all of the specified components. + */ + public static void setEnabled(boolean aBool, Component... componentArr) + { + for (Component aComp : componentArr) + aComp.setEnabled(aBool); + } + + /** + * Utility method that will call doClick() on the selected RadioButton + */ + public static void doClickSelectedButton(JRadioButton... buttonArr) + { + for (int c1 = 0; c1 < buttonArr.length; c1++) + { + if (buttonArr[c1].isSelected() == true) + buttonArr[c1].doClick(); + } + } + + /** + * Utility method that takes up to 8 buttons and converts all of their selection states to a single byte. + */ + public static byte getSelectionStateAsByte(AbstractButton... buttonArr) + { + byte retByte; + + if (buttonArr.length > 8) + throw new RuntimeException("Improper API call. Max of 8 buttons supported. Passed: " + buttonArr.length); + + retByte = 0; + for (int c1 = 0; c1 < buttonArr.length; c1++) + { + if (buttonArr[c1].isSelected() == true) + retByte |= 1 << c1; + } + + return retByte; + } + + /** + * Utility method that takes up to 8 buttons and configures the selection state of the buttons to match the bit + * pattern of aByte. + */ + public static void setSelectionState(byte aByte, AbstractButton... buttonArr) + { + boolean aBool; + + if (buttonArr.length > 8) + throw new RuntimeException("Improper API call. Max of 8 buttons supported. Passed: " + buttonArr.length); + + for (int c1 = 0; c1 < buttonArr.length; c1++) + { + aBool = false; + if (((0x01 << c1) & aByte) != 0) + aBool = true; + + buttonArr[c1].setSelected(aBool); + } + } + + /** + * Utility method to update a JSlider without triggering notifications to its registered listeners. + */ + public static void updateSlider(JSlider aSlider, int aVal) + { + ChangeListener[] tmpArr; + + tmpArr = aSlider.getChangeListeners(); + + for (ChangeListener aListener : tmpArr) + aSlider.removeChangeListener(aListener); + + aSlider.setValue(aVal); + + for (ChangeListener aListener : tmpArr) + aSlider.addChangeListener(aListener); + } + + /** + * Utility method that checks to ensure the current thread is running on the ATW thread. If it is NOT then the + * specified function will be posted so that it is called on the AWT thread. If it is running on the AWT thread then + * nothing will happen and this method will return false. + *

+ * Typically this utility method is called at the start of a function to ensure it is on the AWT thread, and if not + * then schedule the function onto the AWT thread. Thus it is strongly advisable that if this method returns true the + * caller should immediately exit. + *

+ * Typical usage within a method: + * + *

+	 * public void actionPerformed(aEvent)
+	 * {
+	 *    // Ensure this method is run on the AWT thread
+	 *    if (redispatchOnAwtIfNeeded(this, "actionPerformed", aEvent) = true)
+	 *       return;
+	 *       
+	 *    // Do normal work ...
+	 * }
+	 * 
+ */ + public static boolean redispatchOnAwtIfNeeded(Object aObj, String methodName, Object... aArgArr) + { + FunctionRunnable aFunctionRunnable; + + // Do nothing if this is the AWT thread + if (SwingUtilities.isEventDispatchThread() == true) + return false; + + aFunctionRunnable = new FunctionRunnable(aObj, methodName, aArgArr); + SwingUtilities.invokeLater(aFunctionRunnable); + return true; + } + +} diff --git a/src/glum/gui/action/ClickAction.java b/src/glum/gui/action/ClickAction.java new file mode 100644 index 0000000..1a230db --- /dev/null +++ b/src/glum/gui/action/ClickAction.java @@ -0,0 +1,22 @@ +package glum.gui.action; + +import java.awt.event.*; +import javax.swing.*; + +public class ClickAction extends AbstractAction +{ + // State vars + protected AbstractButton target; + + public ClickAction(AbstractButton aTarget) + { + target = aTarget; + } + + @Override + public void actionPerformed(ActionEvent e) + { + target.doClick(); + } + +} diff --git a/src/glum/gui/action/MakeVisibleAction.java b/src/glum/gui/action/MakeVisibleAction.java new file mode 100644 index 0000000..c63fead --- /dev/null +++ b/src/glum/gui/action/MakeVisibleAction.java @@ -0,0 +1,23 @@ +package glum.gui.action; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +public class MakeVisibleAction extends AbstractAction +{ + // State vars + protected Component target; + + public MakeVisibleAction(Component aTarget) + { + target = aTarget; + } + + @Override + public void actionPerformed(ActionEvent e) + { + target.setVisible(true); + } + +} diff --git a/src/glum/gui/component/GComboBox.java b/src/glum/gui/component/GComboBox.java new file mode 100644 index 0000000..45e18cb --- /dev/null +++ b/src/glum/gui/component/GComboBox.java @@ -0,0 +1,171 @@ +package glum.gui.component; + +import glum.gui.component.model.GComboBoxModel; + +import java.awt.event.ActionListener; +import java.util.*; +import javax.swing.JComboBox; +import javax.swing.ListCellRenderer; + +public class GComboBox extends JComboBox +{ + // State vars + protected GComboBoxModel itemModel; + + public GComboBox() + { + itemModel = new GComboBoxModel(new LinkedList()); + setModel(itemModel); + } + + public GComboBox(ActionListener aListener, ListCellRenderer aRenderer) + { + this(); + + addActionListener(aListener); + setRenderer(aRenderer); + } + + public GComboBox(ActionListener aListener) + { + this(); + + addActionListener(aListener); + } + + public GComboBox(ActionListener aListener, List aItemList) + { + itemModel = new GComboBoxModel(aItemList); + setModel(itemModel); + + addActionListener(aListener); + } + + public GComboBox(ActionListener aListener, G1... aItemArr) + { + itemModel = new GComboBoxModel(aItemArr); + setModel(itemModel); + + addActionListener(aListener); + } + + /** + * Returns the list of all items stored in the GComboBox + */ + public ArrayList getAllItems() + { + return itemModel.getAllItems(); + } + + /** + * Returns the items that is currently selected. + */ + public G1 getChosenItem() + { + return itemModel.getSelectedItem(); + } + + /** + * Sets in the currently selected item. This method will not trigger an ActionEvent. + * @see JComboBox#setSelectedItem + */ + public void setChosenItem(G1 aItem) + { + ActionListener[] listenerArr; + + listenerArr = getActionListeners(); + for (ActionListener aListener : listenerArr) + removeActionListener(aListener); + + itemModel.setSelectedItem(aItem); + + for (ActionListener aListener : listenerArr) + addActionListener(aListener); + } + + /** + * Note aItem must be of the generified type. This method will not trigger an ActionEvent. + */ + @Override @SuppressWarnings("unchecked") + public void addItem(Object aItem) + { + ActionListener[] listenerArr; + + listenerArr = getActionListeners(); + for (ActionListener aListener : listenerArr) + removeActionListener(aListener); + + itemModel.addItem((G1)aItem); + + for (ActionListener aListener : listenerArr) + addActionListener(aListener); + } + + /** + * Note aItem must be of the generified type. This method will not trigger an ActionEvent. + */ + public void addItems(G1... aItemArr) + { + ActionListener[] listenerArr; + + listenerArr = getActionListeners(); + for (ActionListener aListener : listenerArr) + removeActionListener(aListener); + + for (G1 aItem : aItemArr) + itemModel.addItem(aItem); + + for (ActionListener aListener : listenerArr) + addActionListener(aListener); + } + + /** + * Note aItem must be of the generified type. This method will not trigger an ActionEvent. + */ + @Override @SuppressWarnings("unchecked") + public void removeItem(Object aItem) + { + ActionListener[] listenerArr; + + listenerArr = getActionListeners(); + for (ActionListener aListener : listenerArr) + removeActionListener(aListener); + + itemModel.removeItem((G1)aItem); + + for (ActionListener aListener : listenerArr) + addActionListener(aListener); + } + + /** + * Removes all of the items from the model. This method will not trigger an ActionEvent. + */ + @Override + public void removeAllItems() + { + ActionListener[] listenerArr; + + listenerArr = getActionListeners(); + for (ActionListener aListener : listenerArr) + removeActionListener(aListener); + + itemModel.removeAllItems(); + + for (ActionListener aListener : listenerArr) + addActionListener(aListener); + } + +// Note you cannot do the below as internal methods within JComboBox make calls to +// the methods below. +// @Override +// public Object getSelectedItem() +// { +// throw new RuntimeException("Unsupported operation. Call getChosenItem()"); +// } +// +// @Override +// public void setSelectedItem(Object aObj) +// { +// throw new RuntimeException("Unsupported operation. Call setChosenItem()"); +// } +} diff --git a/src/glum/gui/component/GComponent.java b/src/glum/gui/component/GComponent.java new file mode 100644 index 0000000..6b26ae1 --- /dev/null +++ b/src/glum/gui/component/GComponent.java @@ -0,0 +1,49 @@ +package glum.gui.component; + +import glum.gui.panel.generic.GenericCodes; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import javax.swing.JComponent; + +import com.google.common.collect.Lists; + +public class GComponent extends JComponent implements GenericCodes +{ + // State vars + protected List myListeners; + + public GComponent() + { + super(); + + myListeners = Lists.newLinkedList(); + } + + /** + * Add an ActionListener to this GPanel + */ + public void addActionListener(ActionListener aListener) + { + myListeners.add(aListener); + } + + /** + * Remove an ActionListener to this GPanel + */ + public void removeActionListener(ActionListener aListener) + { + myListeners.remove(aListener); + } + + /** + * Send out notification to all of the ActionListeners + */ + public void notifyListeners(Object aSource, int aId, String aCommand) + { + for (ActionListener aListener : myListeners) + aListener.actionPerformed(new ActionEvent(aSource, aId, aCommand)); + } + +} diff --git a/src/glum/gui/component/GFancyLabel.java b/src/glum/gui/component/GFancyLabel.java new file mode 100644 index 0000000..c787cc0 --- /dev/null +++ b/src/glum/gui/component/GFancyLabel.java @@ -0,0 +1,93 @@ +package glum.gui.component; + +import java.awt.Dimension; +import java.awt.Font; + +import glum.unit.UnitListener; +import glum.unit.UnitProvider; + +import javax.swing.JLabel; + +public class GFancyLabel extends JLabel implements UnitListener +{ + // State vars + protected UnitProvider[] unitProviderArr; + protected String[] formatStrArr; + protected Object[] refValueArr; + + /** + * Fancy JLabel which provides auto formatting of objects. The constructor + * is provided with a formatStr which has unit place holders specified by "%u" + * There must be a corresponding UnitProvider for each occurrence of "%u". This + * is provided via the variable arguments of UnitProvider. + */ + public GFancyLabel(String formatStr, UnitProvider... aUnitProviderArr) + { + this(null, formatStr, aUnitProviderArr); + } + + /** + * Fancy JLabel which provides auto formatting of objects. The constructor + * is provided with a formatStr which has unit place holders specified by "%u" + * There must be a corresponding UnitProvider for each occurrence of "%u". This + * is provided via the variable arguments of UnitProvider. + */ + public GFancyLabel(Font aFont, String aFormatStr, UnitProvider... aUnitProviderArr) + { + super(); + + if (aFont != null) + setFont(aFont); + + formatStrArr = aFormatStr.split("%u", -1); + + unitProviderArr = aUnitProviderArr; + for (UnitProvider aUnitProvider : unitProviderArr) + aUnitProvider.addListener(this); + + // Insanity check + if (unitProviderArr.length != formatStrArr.length - 1) + throw new RuntimeException("Num place holders: " + (formatStrArr.length - 1) + " Num units: " + unitProviderArr.length); + + refValueArr = new Object[unitProviderArr.length]; + for (int c1 = 0; c1 < unitProviderArr.length; c1++) + refValueArr[c1] = null; + + setMinimumSize(new Dimension(0, 0)); + } + + @Override + public void unitChanged(UnitProvider aProvider, String aKey) + { + setValues(refValueArr); + } + + /** + * Method to set in the set of values which will be formatted with the associated UnitProviders + * which were specified via the constructor. + */ + public void setValues(Object... aValueArr) + { + String aStr; + + // Ensure the number of objects matches the number of units + if (unitProviderArr.length != aValueArr.length) + throw new RuntimeException("Inproper number of arguments. Expected: " + unitProviderArr.length + " Recieved:" + aValueArr.length); + + for (int c1 = 0; c1 < aValueArr.length; c1++) + refValueArr[c1] = aValueArr[c1]; + + aStr = ""; + for (int c1 = 0; c1 < aValueArr.length; c1++) + { + aStr += formatStrArr[c1]; + aStr += unitProviderArr[c1].getUnit().getString(aValueArr[c1], false); + } + + if (formatStrArr.length > aValueArr.length) + aStr += formatStrArr[formatStrArr.length - 1]; + + setText(aStr); + } + +} diff --git a/src/glum/gui/component/GLabel.java b/src/glum/gui/component/GLabel.java new file mode 100644 index 0000000..005ba91 --- /dev/null +++ b/src/glum/gui/component/GLabel.java @@ -0,0 +1,82 @@ +package glum.gui.component; + +import java.awt.Dimension; +import java.awt.Font; + +import glum.unit.ConstUnitProvider; +import glum.unit.Unit; +import glum.unit.UnitListener; +import glum.unit.UnitProvider; + +import javax.swing.JLabel; + +public class GLabel extends JLabel implements UnitListener +{ + // State vars + protected UnitProvider refUnitProvider; + protected Object refValue; + protected boolean showLabel; + + public GLabel(Font aFont) + { + this(null, aFont, false); + } + + public GLabel(Unit aUnit, Font aFont) + { + this(new ConstUnitProvider(aUnit), aFont, false); + } + + public GLabel(UnitProvider aUnitProvider, Font aFont) + { + this(aUnitProvider, aFont, false); + } + + public GLabel(UnitProvider aUnitProvider, Font aFont, boolean aShowLabel) + { + super(); + + refUnitProvider = aUnitProvider; + if (refUnitProvider != null) + refUnitProvider.addListener(this); + refValue = null; + + if (aFont != null) + setFont(aFont); + + showLabel = aShowLabel; + + setMinimumSize(new Dimension(0, 0)); + } + + @Override + public void unitChanged(UnitProvider aProvider, String aKey) + { + setValue(refValue); + } + + /** + * Method to set in the value which will be formatted with the associated unit. + */ + public void setValue(Object aValue) + { + String aStr; + Unit aUnit; + + refValue = aValue; + + aUnit = null; + if (refUnitProvider != null) + aUnit = refUnitProvider.getUnit(); + + if (aUnit == null) + aStr = "" + aValue; + else if (showLabel == false) + aStr = aUnit.getString(aValue); + else + aStr = aUnit.getString(aValue, true); + + setText(aStr); + } + +} diff --git a/src/glum/gui/component/GList.java b/src/glum/gui/component/GList.java new file mode 100644 index 0000000..34416ed --- /dev/null +++ b/src/glum/gui/component/GList.java @@ -0,0 +1,159 @@ +package glum.gui.component; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.SwingUtilities; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import glum.gui.component.model.GListModel; +import glum.gui.panel.generic.GenericCodes; + +import com.google.common.collect.Lists; + +public class GList extends JComponent implements GenericCodes, ListSelectionListener +{ + // Gui vars + private JList refList; + private GListModel refModel; + + // State vars + private ActionListener refActionListener; + private ListSelectionListener refListSelectionListener; + + public GList(Object aListener, List aItemList) + { + refActionListener = null; + refListSelectionListener = null; + + refModel = new GListModel(aItemList); + refList = new JList(refModel); + buildGui(aListener); + } + + public GList(Object aListener, G1... aItemArr) + { + refActionListener = null; + refListSelectionListener = null; + + refModel = new GListModel(aItemArr); + refList = new JList(refModel); + buildGui(aListener); + } + + /** + * Returns the (first) selected item + */ + public G1 getSelectedItem() + { + int selectedIndex; + + selectedIndex = refList.getSelectedIndex(); + if (selectedIndex == -1) + return null; + + return refModel.getElementAt(selectedIndex); + } + + /** + * Returns all of the selected items + */ + public List getSelectedItems() + { + ArrayList retList; + int[] indexArr; + + indexArr = refList.getSelectedIndices(); + + retList = Lists.newArrayList(); + for (int aIndex : indexArr) + retList.add(refModel.getElementAt(aIndex)); + + retList.trimToSize(); + return retList; + } + + /** + * Replaces all of the items in the list with aItemList. + */ + public void setItems(Collection aItemList) + { + refList.removeListSelectionListener(this); + refList.setModel(new GListModel(aItemList)); + refList.addListSelectionListener(this); + } + + /** + * Sets aItem as the selected item. + */ + public void setSelectedItem(G1 aItem) + { + refList.removeListSelectionListener(this); + refList.setSelectedIndex(refModel.indexOf(aItem)); + refList.addListSelectionListener(this); + } + + /** + * Sets all of the items in aItemList as selected + */ + public void setSelectedItems(List aItemList) + { + int[] idArr; + int c1; + + // Ensure we are executed only on the proper thread + if (SwingUtilities.isEventDispatchThread() == false) + throw new RuntimeException("GList.selectItems() not executed on the AWT event dispatch thread."); + + refList.removeListSelectionListener(this); + + c1 = 0; + idArr = new int[aItemList.size()]; + for (G1 aItem : aItemList) + { + idArr[c1] = refModel.indexOf(aItem); + c1++; + } + + refList.setSelectedIndices(idArr); + + refList.addListSelectionListener(this); + } + + @Override + public void setEnabled(boolean aBool) + { + refList.setEnabled(aBool); + } + + @Override + public void valueChanged(ListSelectionEvent aEvent) + { + if (refListSelectionListener != null) + refListSelectionListener.valueChanged(new ListSelectionEvent(this, aEvent.getFirstIndex(), aEvent.getLastIndex(), aEvent.getValueIsAdjusting())); + + if (refActionListener != null) + refActionListener.actionPerformed(new ActionEvent(this, ID_UPDATE, "update")); + } + + /** + * Helper method used to build the GUI + */ + private void buildGui(Object aListener) + { + if (aListener instanceof ActionListener) + refActionListener = (ActionListener)aListener; + if (aListener instanceof ListSelectionListener) + refListSelectionListener = (ListSelectionListener)aListener; + + setLayout(new BorderLayout()); + add(refList); + } + +} diff --git a/src/glum/gui/component/GNumberField.java b/src/glum/gui/component/GNumberField.java new file mode 100644 index 0000000..5ed1f85 --- /dev/null +++ b/src/glum/gui/component/GNumberField.java @@ -0,0 +1,298 @@ +package glum.gui.component; + +import java.awt.Color; +import java.awt.event.ActionListener; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; + +import glum.gui.document.NumberDocument; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.Unit; +import glum.unit.UnitListener; +import glum.unit.UnitProvider; + +public class GNumberField extends JTextField implements DocumentListener, UnitListener +{ + // State vars + protected UnitProvider refUnitProvider; + protected double currValue, minValue, maxValue; + protected boolean isMutating; + + // Gui vars + protected Color failColor, passColor; + protected NumberDocument myDocument; + + /** + * Constructor + * + * @param aListener + * : Default ActionListener + * @param aUnit + * : Object used to format programatic entered values. Note aUnitProvider will also be used to determine if + * Floating or only Integral input is allowed. + * @param inputType + * : Type of input to accept (Integer, Double, etc...) + * @param aMinVal + * : Minimum value to accept + * @param aMaxVal + * : Maximum value to accept + */ + public GNumberField(ActionListener aListener, UnitProvider aUnitProvider, double aMinVal, double aMaxVal) + { + super("", 0); + + refUnitProvider = aUnitProvider; + currValue = 0; + minValue = aMinVal; + maxValue = aMaxVal; + isMutating = false; + + failColor = Color.RED.darker(); + passColor = getForeground(); + + // Register the ActionListener + if (aListener != null) + addActionListener(aListener); + + // Form the appropriate Document and initialize + myDocument = new NumberDocument(this, false); + super.setDocument(myDocument); + + // Register for events of interest + myDocument.addDocumentListener(this); + refUnitProvider.addListener(this); + + // Install our unit into the Document. Note this should be done last as the method installUnit() assumes that + // this GNumberField is already registered with myDocument (via the method call forceTF()). + installUnit(); + } + + public GNumberField(ActionListener aListener, Unit aUnit, double aMinVal, double aMaxVal) + { + this(aListener, new ConstUnitProvider(aUnit), aMinVal, aMaxVal); + } + + /** + * Returns whether the current input is valid + */ + public boolean isValidInput() + { + Unit aUnit; + double modelVal; + + // Ensure we have valid input + aUnit = refUnitProvider.getUnit(); + + modelVal = aUnit.parseString(this.getText(), Double.NaN); + if (Double.isNaN(modelVal) == true) + return false; + + // Ensure the value is within range + if (modelVal < minValue || modelVal > maxValue) + return false; + + return true; + } + + /** + * Sets the currently stored model value to Double.NaN. Also clears out the input area. This method will not trigger + * an ActionEvent. + */ + public void clearValue() + { + currValue = Double.NaN; + + myDocument.removeDocumentListener(this); + setText(""); + setCaretPosition(0); + myDocument.addDocumentListener(this); + } + + /** + * Returns the currently stored model value + */ + public double getValue() + { + return currValue; + } + + /** + * Returns the currently stored model value as an integer. If the modelValue is NaN, then errorVal will be returned. + * The values MaxInt, MinInt are returned for Infinity. + */ + public int getValueAsInt(int errorVal) + { + if (Double.isNaN(currValue) == true) + return errorVal; + + return (int)currValue; + } + + /** + * Takes in a model value and will display it with respect to the active unit. This method will not trigger an + * ActionEvent. + *

+ * Note this method will do nothing if the UI is being "mutated" when this method is called. + */ + public void setValue(final double aValue) + { + // Bail if we are being mutated. The alternative is to throw an exception like: + // throw new IllegalStateException("Attempt to mutate in notification"); + if (isMutating == true) + return; + + // Simple edit if we are not currently being mutated + forceTF(aValue); + updateGui(); + } + + /** + * Changes the range of acceptable values (in model units). Note the current value will be force to fit this range. + */ + public void setMinMaxValue(double aMinValue, double aMaxValue) + { + Unit aUnit; + + minValue = aMinValue; + maxValue = aMaxValue; + if (currValue < minValue || currValue > maxValue) + currValue = minValue; + + // Update our document + aUnit = refUnitProvider.getUnit(); + myDocument.setMinMaxValue(aUnit.toUnit(minValue), aUnit.toUnit(maxValue)); + } + + @Override + public void setDocument(Document aDoc) + { +// throw new UnsupportedOperationException(); + if (aDoc != null) + aDoc.addDocumentListener(this); + + super.setDocument(aDoc); + } + + @Override + public void changedUpdate(DocumentEvent aEvent) + { + syncValue(aEvent); + } + + @Override + public void insertUpdate(DocumentEvent aEvent) + { + syncValue(aEvent); + } + + @Override + public void removeUpdate(DocumentEvent aEvent) + { + syncValue(aEvent); + } + + @Override + public void unitChanged(UnitProvider aProvider, String aKey) + { + installUnit(); + } + + /** + * Updates the internal model value and will update the display wrt to the active unit. + */ + protected void forceTF(double aValue) + { + Unit aUnit; + String aStr; + + // Save off the new model value, and check the validity + currValue = aValue; + if (currValue < minValue || currValue > maxValue) + currValue = Double.NaN; +// throw new RuntimeException("Programatic input is invalid. Is unit compatible? Input: " + aValue); + + // Invalid values shall just clear the text field and bail + if (Double.isNaN(currValue) == true) + { + clearValue(); + return; + } + + // Convert from model value to (unit) textual format + aUnit = refUnitProvider.getUnit(); + aStr = aUnit.getString(currValue); + + // Update the GUI internals + myDocument.removeDocumentListener(this); + setText(aStr); + setCaretPosition(0); + myDocument.addDocumentListener(this); + } + + /** + * Helper method to update the associated Document whenever a Unit is changed + */ + protected void installUnit() + { + Unit aUnit; + boolean aBool; + + // Ensure that we have a valid Unit + aUnit = refUnitProvider.getUnit(); + if (aUnit instanceof NumberUnit == false) + throw new RuntimeException("refUnitProvider must return a Unit of type NumberUnit. Unit: " + aUnit); + + // Update our Document to reflect whether this Unit supports floating point numbers + aBool = (aUnit instanceof NumberUnit) && (((NumberUnit)aUnit).isFloating() == true); + myDocument.setAllowFloats(aBool); + + // Update the Document's MinMax values reflect the new Unit + setMinMaxValue(minValue, maxValue); + + // Force myDocument's text to match the new unit + forceTF(currValue); + } + + /** + * Keeps the "model" value conceptually linked to the GUI component. It will also trigger the actionEventListeners. + */ + protected void syncValue(DocumentEvent e) + { + Unit aUnit; + + // Mark ourself as mutating + isMutating = true; + + // Convert the textual (unit) value to the model value + aUnit = refUnitProvider.getUnit(); + currValue = aUnit.parseString(this.getText(), Double.NaN); + + // If the value is not in range then, it is invalid + if (currValue < minValue || currValue > maxValue) + currValue = Double.NaN; + + // Notify our listeners and update the GUI + updateGui(); + fireActionPerformed(); + + // We are no longer mutating + isMutating = false; + } + + /** + * Helper method to update the GUI to reflect the current state of the NumberField + */ + protected void updateGui() + { + Color aColor; + + aColor = passColor; + if (isValidInput() == false) + aColor = failColor; + + setForeground(aColor); + } + +} diff --git a/src/glum/gui/component/GPasswordField.java b/src/glum/gui/component/GPasswordField.java new file mode 100644 index 0000000..2d172e9 --- /dev/null +++ b/src/glum/gui/component/GPasswordField.java @@ -0,0 +1,106 @@ +package glum.gui.component; + +import java.awt.event.ActionListener; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; + +public class GPasswordField extends JPasswordField implements DocumentListener +{ + /** + * Constructor + * + * @param aListener + * : Default ActionListener + */ + public GPasswordField(ActionListener aListener) + { + super("", 0); + + if (aListener != null) + addActionListener(aListener); + + // Form the appropriate Document + Document doc; + doc = new PlainDocument(); + super.setDocument(doc); + + // Register for events of interest + doc.addDocumentListener(this); + } + + /** + * Returns whether the current input is valid + */ + public boolean isValidInput() + { + if (getPassword().length < 2) + return false; + + return true; + } + + /** + * Sets in the value for the GTextField. Note this method will not trigger an ActionEvent. + */ + public void setValue(String aText) + { + forceTF(aText); + } + + @Override + public void setDocument(Document aDoc) + { + // throw new UnsupportedOperationException(); + if (aDoc != null) + aDoc.addDocumentListener(this); + + super.setDocument(aDoc); + } + + @Override + public void changedUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + @Override + public void insertUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + @Override + public void removeUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + /** + * Updates the internal model value and will update the display wrt tho active unit. + */ + protected void forceTF(String aStr) + { + Document aDocument; + + // Update the GUI internals + aDocument = this.getDocument(); + if (aDocument != null) + aDocument.removeDocumentListener(this); + + setText(aStr); + setCaretPosition(0); + + if (aDocument != null) + aDocument.addDocumentListener(this); + } + + /** + * Keeps the "model" value conceptually linked to the GUI component. It will also trigger the actionEventListeners. + */ + protected void syncValue(DocumentEvent aEvent) + { + fireActionPerformed(); + } + +} diff --git a/src/glum/gui/component/GSlider.java b/src/glum/gui/component/GSlider.java new file mode 100644 index 0000000..2299e86 --- /dev/null +++ b/src/glum/gui/component/GSlider.java @@ -0,0 +1,91 @@ +package glum.gui.component; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class GSlider extends JSlider implements ChangeListener +{ + private ActionListener myListener; + private double minVal, maxVal, rngVal; + private int maxSteps; + + public GSlider(ActionListener aListener, int aMaxSteps, double aMinVal, double aMaxVal) + { + super(0, aMaxSteps); + addChangeListener(this); + + myListener = aListener; + + maxSteps = aMaxSteps; + + minVal = aMinVal; + maxVal = aMaxVal; + rngVal = maxVal - minVal; + } + + public GSlider(ActionListener aListener, double aMinVal, double aMaxVal) + { + this(aListener, 1000, aMinVal, aMaxVal); + } + + /** + * Returns the model value for which this slider is currently set to. + *

+ * Use this method over {@link JSlider#getValue()} + */ + public double getModelValue() + { + double retVal; + + retVal = minVal + ((super.getValue() / (double)maxSteps) * rngVal); + return retVal; + } + + /** + * Takes in the model's minVal and maxVal range. The current chosen model value will be adjusted to be in the middle + * of the range. + */ + public void setModelRange(double aMinVal, double aMaxVal) + { + minVal = aMinVal; + maxVal = aMaxVal; + rngVal = maxVal - minVal; + + setModelValue(minVal + rngVal / 2); + } + + /** + * Takes in a model value and will adjust the slider to display the value. Note this method will not trigger an + * ActionEvent. + *

+ * Use this method over {@link JSlider#setValue} + */ + public void setModelValue(double aVal) + { + double guiVal; + + guiVal = ((aVal - minVal) / rngVal) * maxSteps; + + removeChangeListener(this); + setValue((int)guiVal); + addChangeListener(this); + } + + @Override + public void stateChanged(ChangeEvent aEvent) + { + notifyLisener(); + } + + /** + * Helper method to notify our listener + */ + private void notifyLisener() + { + myListener.actionPerformed(new ActionEvent(this, 0, "update")); + } + +} diff --git a/src/glum/gui/component/GTextField.java b/src/glum/gui/component/GTextField.java new file mode 100644 index 0000000..61e96b7 --- /dev/null +++ b/src/glum/gui/component/GTextField.java @@ -0,0 +1,114 @@ +package glum.gui.component; + +import java.awt.event.ActionListener; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; + +public class GTextField extends JTextField implements DocumentListener +{ + /** + * Constructor + * + * @param aListener + * : Default ActionListener + */ + public GTextField(ActionListener aListener) + { + super("", 0); + + if (aListener != null) + addActionListener(aListener); + + // Form the appropriate Document + Document doc; + doc = new PlainDocument(); + super.setDocument(doc); + + // Register for events of interest + doc.addDocumentListener(this); + } + + /** + * Returns whether the current input is valid + */ + public boolean isValidInput() + { + if (getText().isEmpty() == true) + return false; + + return true; + } + + /** + * Returns the currently stored model value + */ + public String getValue() + { + return getText(); + } + + /** + * Sets in the value for the GTextField. Note this method will not trigger an ActionEvent. + */ + public void setValue(String aText) + { + forceTF(aText); + } + + @Override + public void setDocument(Document aDoc) + { + // throw new UnsupportedOperationException(); + if (aDoc != null) + aDoc.addDocumentListener(this); + + super.setDocument(aDoc); + } + + @Override + public void changedUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + @Override + public void insertUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + @Override + public void removeUpdate(DocumentEvent aEvent) + { + fireActionPerformed(); + } + + /** + * Updates the internal model value and will update the display wrt tho active unit. + */ + protected void forceTF(String aStr) + { + Document aDocument; + + // Update the GUI internals + aDocument = this.getDocument(); + if (aDocument != null) + aDocument.removeDocumentListener(this); + + setText(aStr); + setCaretPosition(0); + + if (aDocument != null) + aDocument.addDocumentListener(this); + } + + /** + * Keeps the "model" value conceptually linked to the GUI component. It will also trigger the actionEventListeners. + */ + protected void syncValue(DocumentEvent aEvent) + { + fireActionPerformed(); + } + +} diff --git a/src/glum/gui/component/GToggle.java b/src/glum/gui/component/GToggle.java new file mode 100644 index 0000000..5043c60 --- /dev/null +++ b/src/glum/gui/component/GToggle.java @@ -0,0 +1,76 @@ +package glum.gui.component; + +import javax.swing.Icon; +import javax.swing.JToggleButton; + +public class GToggle extends JToggleButton +{ + // State vars + protected boolean isActive; + + // Gui vars + protected Icon falseIcon, trueIcon; + + public GToggle(Icon aFalseIcon, Icon aTrueIcon, boolean aIsActive) + { + super(); + + falseIcon = aFalseIcon; + trueIcon = aTrueIcon; + setSelected(aIsActive); + + setModel(new GToggleButtonModel()); + } + + @Override + public void setSelected(boolean b) + { + super.setSelected(b); + updateGui(); + } + + @Override + public void doClick(int pressTime) + { + super.doClick(pressTime); + updateGui(); + } + + /** + * Utility method + */ + private void updateGui() + { + if (isSelected() == true) + setIcon(trueIcon); + else + setIcon(falseIcon); + } + + /** + * The ToggleButton model + *

+ * Warning: Serialized objects of this class will not be compatible with future Swing releases. The + * current serialization support is appropriate for short term storage or RMI between applications running the same + * version of Swing. As of 1.4, support for long term storage of all JavaBeansTM + * has been added to the java.beans package. Please see {@link java.beans.XMLEncoder}. + */ + public class GToggleButtonModel extends ToggleButtonModel + { + /** + * Creates a new ToggleButton Model + */ + public GToggleButtonModel() + { + } + + @Override + public void setSelected(boolean b) + { + super.setSelected(b); + + updateGui(); + } + } + +} diff --git a/src/glum/gui/component/banner/Banner.java b/src/glum/gui/component/banner/Banner.java new file mode 100644 index 0000000..566cac7 --- /dev/null +++ b/src/glum/gui/component/banner/Banner.java @@ -0,0 +1,222 @@ +package glum.gui.component.banner; + +import java.awt.*; +import java.awt.geom.*; +import java.util.*; +import javax.swing.*; + +import glum.gui.*; +import glum.io.token.MatchTokenizer; +import glum.io.token.Tokenizer; + +public class Banner extends JComponent +{ + // Our configuration + protected BannerConfig myConfig; + + // Current message to display + protected String displayMsg; + + /** + * Constructor + */ + public Banner() + { + super(); + + myConfig = new BannerConfig(); + setConfig(myConfig); + } + + @Override + public void paintComponent(Graphics g) + { + Graphics2D g2d; + Rectangle2D aRect; + double winW, winH, msgW, msgH; + double sX, sY, offSetY; + + super.paintComponent(g); + g2d = (Graphics2D)g; + + // Determine the window boundaries + winW = getWidth(); + winH = getHeight(); + + // Compute the displayMsg boundaries + aRect = myConfig.font.getStringBounds(displayMsg, g2d.getFontRenderContext()); + msgW = aRect.getWidth(); + msgH = aRect.getHeight(); + offSetY = msgH - aRect.getMaxY(); + + // Form our rectangle + aRect = new Rectangle2D.Double(0, 0, winW, winH); + + // Draw the background + if (myConfig.bgColor != null) + { + g2d.setColor(myConfig.bgColor); + g2d.fill(aRect); + } + + // Draw the border + if (myConfig.borderColor != null && myConfig.borderWidth > 0) + { + g2d.setColor(myConfig.borderColor); + g2d.setStroke(new BasicStroke(myConfig.borderWidth)); + g2d.draw(aRect); + } + + // Draw the displayMsg + sX = winW / 2.0 - msgW / 2.0; + sY = winH / 2.0 - msgH / 2.0; + g2d.setFont(myConfig.font); + g2d.setColor(myConfig.fgColor); + g2d.drawString(displayMsg, (int)sX, (int)(sY + offSetY)); + } + + /** + * setConfig - Sets in the new banner attributes + */ + public void setConfig(BannerConfig aConfig) + { + // Insanity check + if (aConfig == null) + return; + myConfig = aConfig; + + // Build the actual displayMsg + if (myConfig.numRepeats > 1) + { + displayMsg = ""; + for (int c1 = 0; c1 < myConfig.numRepeats; c1++) + displayMsg += myConfig.bannerMsg; + } + else + { + displayMsg = myConfig.bannerMsg; + } + + // Time for a repaint + repaint(); + } + + /** + * Utility method that converts a string to a BannerConfig. Eventually this method class should be + * moved to a utility class of sorts. + */ + public static BannerConfig readBannerConfig(String strLine) + { + BannerConfig aConfig; + Collection instrSet, parmSet; + Tokenizer instrTokenizer, parmTokenizer; + String parms[]; + + // Insanity check + if (strLine == null) + return null; + + // Build our tokenizers + instrTokenizer = new MatchTokenizer("[^/]+"); + parmTokenizer = new MatchTokenizer("[^\\,]+"); + + // Get the set of embedded instructions + instrSet = instrTokenizer.getTokens(strLine); + + // Process the instruction + aConfig = new BannerConfig(); + for (String aInstr : instrSet) + { + parmSet = parmTokenizer.getTokens(aInstr); + parms = parmSet.toArray(new String[] {""}); + + if (parms.length == 0) + { + ; // Nothing to process + } + else if (parms[0].equalsIgnoreCase("refName") == true && parms.length == 2) + { + aConfig.refName = parms[1]; + } + else if (parms[0].equalsIgnoreCase("label") == true && parms.length == 2) + { + aConfig.bannerMsg = parms[1]; + } + else if (parms[0].equalsIgnoreCase("font") == true && parms.length >= 2) + { + String face; + int style, size; + boolean bold, italic; + + face = parms[1]; + + size = 12; + if (parms.length > 2) + size = GuiUtil.readInt(parms[2], 12); + + bold = false; + if (parms.length > 3) + bold = GuiUtil.readBoolean(parms[3], false); + + italic = false; + if (parms.length > 4) + italic = GuiUtil.readBoolean(parms[4], false); + + style = 0; + if (bold == false && italic == false) + style = Font.PLAIN; + if (bold == true) + style = Font.BOLD; + if (italic == true) + style |= Font.ITALIC; + + aConfig.font = new Font(face, style, size); + } + else if (parms[0].equalsIgnoreCase("fgColor") == true && parms.length == 4) + { + int r, g, b; + + r = GuiUtil.readRangeInt(parms[1], 0, 255, 255); + g = GuiUtil.readRangeInt(parms[2], 0, 255, 255); + b = GuiUtil.readRangeInt(parms[3], 0, 255, 255); + aConfig.fgColor = new Color(r, g, b); + } + else if (parms[0].equalsIgnoreCase("bgColor") == true && parms.length >= 4) + { + int r, g, b, a; + + r = GuiUtil.readRangeInt(parms[1], 0, 255, 255); + g = GuiUtil.readRangeInt(parms[2], 0, 255, 255); + b = GuiUtil.readRangeInt(parms[3], 0, 255, 255); + + a = 255; + if (parms.length > 4) + a = GuiUtil.readRangeInt(parms[4], 0, 255, 255); + + aConfig.bgColor = new Color(r, g, b, a); + } + else if (parms[0].equalsIgnoreCase("border") == true && parms.length >= 4) + { + int r, g, b; + + r = GuiUtil.readRangeInt(parms[1], 0, 255, 255); + g = GuiUtil.readRangeInt(parms[2], 0, 255, 255); + b = GuiUtil.readRangeInt(parms[3], 0, 255, 255); + aConfig.borderColor = new Color(r, g, b); + + if (parms.length > 4) + aConfig.borderWidth = GuiUtil.readRangeInt(parms[4], 0, 10, 0); + + if (parms.length > 5) + aConfig.borderPad = GuiUtil.readRangeInt(parms[5], -20, 20, 0); + } + else if (parms[0].equalsIgnoreCase("repeatMsg") == true && parms.length == 2) + { + aConfig.numRepeats = GuiUtil.readRangeInt(parms[1], -1, 100, 0); + } + } + + return aConfig; + } + +} diff --git a/src/glum/gui/component/banner/BannerConfig.java b/src/glum/gui/component/banner/BannerConfig.java new file mode 100644 index 0000000..11a8a4d --- /dev/null +++ b/src/glum/gui/component/banner/BannerConfig.java @@ -0,0 +1,36 @@ +package glum.gui.component.banner; + +import java.awt.*; + +public class BannerConfig +{ + // State vars + public String refName; + + public Color bgColor, fgColor; + public Color borderColor; + public int borderWidth, borderPad; + + public Font font; + public int numRepeats; + public String bannerMsg; + + /** + * Constructor + */ + public BannerConfig() + { + refName = "DEFAULT"; + + bgColor = Color.BLACK; + fgColor = Color.WHITE; + borderColor = null; + borderWidth = 0; + borderPad = 0; + + font = new Font("Serif", Font.PLAIN, 12); + numRepeats = 1; + bannerMsg = "Banner"; + } + +} diff --git a/src/glum/gui/component/model/GComboBoxModel.java b/src/glum/gui/component/model/GComboBoxModel.java new file mode 100644 index 0000000..e9558b7 --- /dev/null +++ b/src/glum/gui/component/model/GComboBoxModel.java @@ -0,0 +1,60 @@ +package glum.gui.component.model; + +import java.util.Collection; +import javax.swing.ComboBoxModel; + +import com.google.common.collect.Lists; + +public class GComboBoxModel extends GListModel implements ComboBoxModel +{ + protected G1 chosenItem; + + public GComboBoxModel(G1... aItemArr) + { + this(Lists.newArrayList(aItemArr)); + } + + public GComboBoxModel(Collection aItemList) + { + super(aItemList); + + chosenItem = null; + if (itemList.size() > 0) + chosenItem = itemList.get(0); + } + + @Override + public void addItem(G1 aItem) + { + if (chosenItem == null) + chosenItem = aItem; + + super.addItem(aItem); + } + + @Override + public void removeItem(G1 aItem) + { + super.removeItem(aItem); + + chosenItem = null; + if (itemList.size() > 0) + chosenItem = itemList.get(0); + } + + /** + * Note aItem must be of the Generified type + */ + @Override @SuppressWarnings("unchecked") + public void setSelectedItem(Object aItem) + { + chosenItem = (G1)aItem; + } + + @Override + public G1 getSelectedItem() + { + return chosenItem; + } + +} diff --git a/src/glum/gui/component/model/GListModel.java b/src/glum/gui/component/model/GListModel.java new file mode 100644 index 0000000..420e81b --- /dev/null +++ b/src/glum/gui/component/model/GListModel.java @@ -0,0 +1,98 @@ +package glum.gui.component.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.swing.AbstractListModel; + +import com.google.common.collect.Lists; + +/** + * Generified mutable ListModel + */ +public class GListModel extends AbstractListModel +{ + protected List itemList; + + public GListModel(Collection aItemList) + { + itemList = Lists.newArrayList(aItemList); + } + + public GListModel(G1... aItemArr) + { + itemList = Lists.newArrayList(aItemArr); + } + + /** + * Adds aItem to this model + */ + public void addItem(G1 aItem) + { + int index; + + itemList.add(aItem); + + index = itemList.size() - 1; + fireIntervalAdded(this, index, index); + } + + /** + * Removes aItem from this model + */ + public void removeItem(G1 aItem) + { + int index; + + index = itemList.indexOf(aItem); + itemList.remove(aItem); + + fireIntervalRemoved(this, index, index); + } + + /** + * Removes all the items from this model + */ + public void removeAllItems() + { + int lastIndex; + + // Bail if the list is empty + if (itemList.isEmpty() == true) + return; + + lastIndex = itemList.size() - 1; + itemList.clear(); + + fireIntervalRemoved(this, 0, lastIndex); + } + + /** + * Returns a list of all the items + */ + public ArrayList getAllItems() + { + return Lists.newArrayList(itemList); + } + + /** + * Returns the index of the item located in this model. + */ + public int indexOf(G1 aItem) + { + return itemList.indexOf(aItem); + } + + @Override + public int getSize() + { + return itemList.size(); + } + + @Override + public G1 getElementAt(int index) + { + return itemList.get(index); + } +} diff --git a/src/glum/gui/dialog/MemoryUtilDialog.java b/src/glum/gui/dialog/MemoryUtilDialog.java new file mode 100644 index 0000000..968e548 --- /dev/null +++ b/src/glum/gui/dialog/MemoryUtilDialog.java @@ -0,0 +1,142 @@ +package glum.gui.dialog; + +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.DecimalFormat; +import javax.swing.*; +import net.miginfocom.swing.MigLayout; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GLabel; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.UnitProvider; + +public class MemoryUtilDialog extends JDialog implements ActionListener +{ + // Gui components + private GLabel totalMemL, freeMemL, usedMemL; + private UnitProvider byteUP; + private JButton closeB, gcRunB, updateB; + + /** + * Constructor + */ + public MemoryUtilDialog(JFrame parentFrame) + { + super(parentFrame); + + setTitle("JVM Memory Usage"); + setDefaultCloseOperation(HIDE_ON_CLOSE); + setModal(false); + + DecimalFormat numFormat; + numFormat = new DecimalFormat(); + numFormat.setGroupingUsed(true); + numFormat.setGroupingSize(3); + numFormat.setMaximumFractionDigits(0); + byteUP = new ConstUnitProvider(new NumberUnit("MB", "MB", 1.0 / (1024 * 1024), numFormat)); + + // Place the dialog in the center + buildGuiArea(); + setLocationRelativeTo(parentFrame); + + // Set up keyboard short cuts + FocusUtil.addWindowKeyBinding(getRootPane(), "ESCAPE", new ClickAction(closeB)); + FocusUtil.addWindowKeyBinding(getRootPane(), "ENTER", new ClickAction(updateB)); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + + source = e.getSource(); + if (source == gcRunB) + { + System.gc(); + updateGui(); + } + else if (source == updateB) + { + updateGui(); + } + else if (source == closeB) + { + setVisible(false); + } + } + + @Override + public void setVisible(boolean isVisible) + { + updateGui(); + super.setVisible(isVisible); + } + + /** + * Forms the actual dialog GUI + */ + private void buildGuiArea() + { + JPanel aPanel; + JLabel tmpL; + Font tmpFont; + + // Form the panel + aPanel = new JPanel(new MigLayout("", "[right][left,grow]", "[][][]10[]10")); + tmpFont = new JTextField().getFont(); + + // Info area + tmpL = new JLabel("Total Memory:"); + totalMemL = new GLabel(byteUP, tmpFont, true); + aPanel.add(tmpL, ""); + aPanel.add(totalMemL, "growx,wrap"); + + tmpL = new JLabel("Free Memory:"); + freeMemL = new GLabel(byteUP, tmpFont, true); + aPanel.add(tmpL, ""); + aPanel.add(freeMemL, "growx,wrap"); + + tmpL = new JLabel("Used Memory:"); + usedMemL = new GLabel(byteUP, tmpFont, true); + aPanel.add(tmpL, ""); + aPanel.add(usedMemL, "growx,wrap"); + + // Control area + updateB = GuiUtil.createJButton("Update", this); + gcRunB = GuiUtil.createJButton("Run GC", this); + closeB = GuiUtil.createJButton("Close", this); + aPanel.add(updateB, "span 2,split 3"); + aPanel.add(gcRunB, ""); + aPanel.add(closeB, ""); + + // Add the main panel into the dialog + getContentPane().add(aPanel); + pack(); + } + + /** + * Utility method to update the Gui + */ + private void updateGui() + { + Runtime aRuntime; + long freeMem, usedMem, totalMem; + + aRuntime = Runtime.getRuntime(); + + // Update the memory usage + freeMem = aRuntime.freeMemory(); + totalMem = aRuntime.totalMemory(); + usedMem = totalMem - freeMem; + + freeMemL.setValue(freeMem); + totalMemL.setValue(totalMem); + usedMemL.setValue(usedMem); + } + +} diff --git a/src/glum/gui/dnd/PlainTransferHandler.java b/src/glum/gui/dnd/PlainTransferHandler.java new file mode 100644 index 0000000..38d7ec9 --- /dev/null +++ b/src/glum/gui/dnd/PlainTransferHandler.java @@ -0,0 +1,52 @@ +package glum.gui.dnd; + +import java.awt.datatransfer.Transferable; + +import javax.swing.JComponent; +import javax.swing.TransferHandler; + +/** + * Generic TransferHandler that supports transferring an arbitrary Transferable. Note that before the DnD transfer + * mechanism is triggered via the method exportAsDrag(), the developer must set in the Transferable via the method + * setWorkTransferable(). + */ +public class PlainTransferHandler extends TransferHandler +{ + // State vars + protected Transferable workTransferable; + + public PlainTransferHandler() + { + super(); + + workTransferable = null; + } + + /** + * Method to set in the current "active" Trasferable for which this TrasferHandler will process. + */ + public void setWorkTransferable(Transferable aTransferable) + { + workTransferable = aTransferable; + } + + @Override + public int getSourceActions(JComponent aComp) + { + return COPY_OR_MOVE; + } + + @Override + public Transferable createTransferable(JComponent aComp) + { + // Just return the active Transferable + return workTransferable; + } + + @Override + public void exportDone(JComponent c, Transferable t, int action) + { + workTransferable = null; + } + +} diff --git a/src/glum/gui/dock/BaseDockable.java b/src/glum/gui/dock/BaseDockable.java new file mode 100644 index 0000000..6d7e266 --- /dev/null +++ b/src/glum/gui/dock/BaseDockable.java @@ -0,0 +1,50 @@ +package glum.gui.dock; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import bibliothek.gui.DockStation; +import bibliothek.gui.dock.DefaultDockable; + +public class BaseDockable extends DefaultDockable +{ + // Tells whether this Dockable can be dragged and dropped to another station + private DockStation homeStation; + private boolean isTransferable; + + public BaseDockable() + { + super(); + + isTransferable = true; + } + + public BaseDockable(JComponent aComp, String aTitle, Icon aIcon) + { + super(aComp, aTitle, aIcon); + + isTransferable = true; + } + + public boolean isTransferable(DockStation aStation) + { + if (isTransferable == true) + return true; + + // We can only be transfered to our homeStation when we are not transferable + return aStation == homeStation; + } + + public void setTransferable(boolean aBool) + { + homeStation = null; + isTransferable = aBool; + + + // Record our parent when we become non transferable + if (isTransferable == false) + homeStation = getDockParent(); + } + + +} diff --git a/src/glum/gui/dock/CloseableDockable.java b/src/glum/gui/dock/CloseableDockable.java new file mode 100644 index 0000000..ff99df5 --- /dev/null +++ b/src/glum/gui/dock/CloseableDockable.java @@ -0,0 +1,34 @@ +package glum.gui.dock; + +import glum.gui.dock.action.Closeable; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import bibliothek.gui.DockFrontend; + +/** + * Dockable class to wrap aComp into a DefaultDockable using the specified title and icon. This Dockable will be + * automatically installed into aFrontend and support proper closing via the {@link Closeable} interface. + */ +public class CloseableDockable extends BaseDockable implements Closeable +{ + protected DockFrontend refFrontend; + + public CloseableDockable(DockFrontend aFrontend, String idName, JComponent aComp, String aTitle, Icon aIcon) + { + super(aComp, aTitle, aIcon); + setTitleIcon(aIcon); + + // Register ourselves with the refFrontend + refFrontend = aFrontend; + refFrontend.addDockable(idName, this); + } + + @Override + public void close() + { + refFrontend.hide(this); + } + +} diff --git a/src/glum/gui/dock/CustomPlaceholderStrategy.java b/src/glum/gui/dock/CustomPlaceholderStrategy.java new file mode 100644 index 0000000..eb1ab6e --- /dev/null +++ b/src/glum/gui/dock/CustomPlaceholderStrategy.java @@ -0,0 +1,61 @@ +package glum.gui.dock; + +import bibliothek.gui.DockStation; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.station.support.PlaceholderStrategy; +import bibliothek.gui.dock.station.support.PlaceholderStrategyListener; +import bibliothek.util.Path; + +/* + * This is our very simple PlaceholderStrategy. It only recognizes our custom + * PrimDockable and returns the associated place holder. + * + * TODO: This class is incomplete. It might be deleted in the future. + */ +public class CustomPlaceholderStrategy implements PlaceholderStrategy +{ + @Override + public void addListener(PlaceholderStrategyListener listener) + { + // ignore + } + + @Override + public Path getPlaceholderFor(Dockable aDockable) + { + // We handle only PrimDockables + if (aDockable instanceof PrimDock == false) + return null; + +//System.out.println("Attempting to add placeholder for dockable: " + aDockable.getTitleText()); +// return new Path("Prim:" + ((PrimDockable)aDockable).getUniqueId()); + return null; + } + + @Override + public void install(DockStation station) + { + // ignore + } + + @Override + public boolean isValidPlaceholder(Path placeholder) + { + System.out.println("isValidPlaceholder()-> Path:" + placeholder.toString()); + + return true; + } + + @Override + public void removeListener(PlaceholderStrategyListener listener) + { + // ignore + } + + @Override + public void uninstall(DockStation station) + { + // ignore + } + +} diff --git a/src/glum/gui/dock/DockUtil.java b/src/glum/gui/dock/DockUtil.java new file mode 100644 index 0000000..a0f4445 --- /dev/null +++ b/src/glum/gui/dock/DockUtil.java @@ -0,0 +1,213 @@ +package glum.gui.dock; + +import java.awt.event.ActionListener; +import java.util.List; +import java.util.Set; + +import javax.swing.Icon; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import bibliothek.gui.DockController; +import bibliothek.gui.DockFrontend; +import bibliothek.gui.DockStation; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.ScreenDockStation; +import bibliothek.gui.dock.SplitDockStation; +import bibliothek.gui.dock.action.DockAction; +import bibliothek.gui.dock.action.actions.SimpleButtonAction; +import bibliothek.gui.dock.station.screen.ScreenFullscreenAction; +import bibliothek.gui.dock.station.split.SplitFullScreenAction; + +public class DockUtil +{ + /** + * Utility method to construct a SimpleButtonAction. + */ + public static SimpleButtonAction createAction(String aText, Icon aIcon, ActionListener aListener) + { + SimpleButtonAction aAction; + + aAction = new SimpleButtonAction(); + aAction.setText(aText); + aAction.setIcon(aIcon); + aAction.addActionListener(aListener); + + return aAction; + } + + /** + * Utility method to return the appropriate DockAction associated with aStation which will cause a "full screen" + * action to be executed. Currently only object of type {@link SplitDockStation} and {@link ScreenDockStation} are + * supported. + */ + public static DockAction createFullScreenAction(DockStation aStation, DockController aController) + { + DockAction fullScreenAction; + + if (aStation instanceof SplitDockStation) + { + fullScreenAction = new SplitFullScreenAction((SplitDockStation)aStation); + ((SplitFullScreenAction)fullScreenAction).setController(aController); + } + else if (aStation instanceof ScreenDockStation) + { + fullScreenAction = new ScreenFullscreenAction((ScreenDockStation)aStation); + ((ScreenFullscreenAction)fullScreenAction).setController(aController); + } + else + { + throw new RuntimeException("Unsupported Dockable type: " + aStation); + } + + return fullScreenAction; + } + + /** + * Method to locate the first Dockable (contained within aStation) which is of type aClass + */ + public static G1 findDockable(DockStation aStation, Class aClass) + { + Dockable evalDockable; + + for (int c1 = 0; c1 < aStation.getDockableCount(); c1++) + { + evalDockable = aStation.getDockable(c1); + if (evalDockable instanceof DockStation) + evalDockable = findDockable((DockStation)evalDockable, aClass); + + if (evalDockable.getClass() == aClass) + return aClass.cast(evalDockable); + } + + return null; + } + + /** + * Method to locate the list of Dockables (contained within aFrontend) which is of one of the types in aClassArr. All + * child DockStations will be searched. + */ + public static List findDockableList(DockFrontend aFrontend, Class... aClassArr) + { + Set> classSet; + List itemList; + + // Transform the match class array to a set + classSet = Sets.newHashSet(); + for (Class aClass : aClassArr) + classSet.add(aClass); + + itemList = Lists.newLinkedList(); + for (DockStation aStation : aFrontend.getRoots()) + findDockableList(aStation, classSet, itemList); + + return itemList; + } + + /** + * Method to locate the list of Dockables (contained within aFrontend) which is of type of aClass. All child + * DockStations will be searched. + */ + public static List findDockableList(DockFrontend aFrontend, Class aClass) + { + Set> classSet; + List itemList; + + // Transform the match class array to a set + classSet = Sets.newHashSet(); + classSet.add(aClass); + + itemList = Lists.newLinkedList(); + for (DockStation aStation : aFrontend.getRoots()) + findDockableList(aStation, classSet, itemList); + + return itemList; + } + + /** + * Method to locate the list of Dockables (contained within aStation) which is of one of the types in aClassArr. All + * child DockStations will be searched. + */ + public static List findDockableList(DockStation aStation, Class... aClassArr) + { + Set> classSet; + List itemList; + + // Transform the match class array to a set + classSet = Sets.newHashSet(); + for (Class aClass : aClassArr) + classSet.add(aClass); + + itemList = Lists.newLinkedList(); + findDockableList(aStation, classSet, itemList); + + return itemList; + } + + /** + * Method to locate the list of Dockables (contained within aStation) which is of type of aClass. All child + * DockStations will be searched. + */ + public static List findDockableList(DockStation aStation, Class aClass) + { + Set> classSet; + List itemList; + + // Transform the match class array to a set + classSet = Sets.newHashSet(); + classSet.add(aClass); + + itemList = Lists.newLinkedList(); + findDockableList(aStation, classSet, itemList); + + return itemList; + } + + /** + * Helper method to remove all PlotGroupStations and ChartDockables + */ + public static void removeAllDockablesOfType(DockFrontend aFrontend, Class... dockTypeArr) + { + List dockList; + DockStation dockStation; + + // Gather all of the Dockables of interest + dockList = DockUtil.findDockableList(aFrontend, dockTypeArr); + + // Remove all of the Dockables + for (Dockable aDock : dockList) + { + dockStation = aDock.getDockParent(); + if (dockStation != null) + dockStation.drag(aDock); + } + } + + /** + * Recursive helper method to locate the list of Dockables (contained within aStation) which is of type aClass. The + * results will be stored in aItemList + */ + @SuppressWarnings("unchecked") + private static void findDockableList(DockStation aStation, Set> aClassSet, List aItemList) + { + Dockable evalDockable; + + for (int c1 = 0; c1 < aStation.getDockableCount(); c1++) + { + evalDockable = aStation.getDockable(c1); + for (Class aClass : aClassSet) + { + if (aClass.isAssignableFrom(evalDockable.getClass()) == true) + { + aItemList.add((G1)evalDockable); + break; + } + } + + if (evalDockable instanceof DockStation) + findDockableList((DockStation)evalDockable, aClassSet, aItemList); + } + } + +} diff --git a/src/glum/gui/dock/FrontendAddConfigPanel.java b/src/glum/gui/dock/FrontendAddConfigPanel.java new file mode 100644 index 0000000..1d512e8 --- /dev/null +++ b/src/glum/gui/dock/FrontendAddConfigPanel.java @@ -0,0 +1,86 @@ +package glum.gui.dock; + +import java.awt.*; +import java.awt.event.ActionEvent; + +import bibliothek.gui.DockFrontend; + +import glum.gui.panel.generic.TextInputPanel; + +public class FrontendAddConfigPanel extends TextInputPanel +{ + // Constants + public static final String DEFAULT_NAME = "Default"; + + // State vars + protected DockFrontend refFrontend; + + /** + * Constructor + */ + public FrontendAddConfigPanel(Component aParent, DockFrontend aFrontend) + { + super(aParent); + + refFrontend = aFrontend; + + // Set in a more specific title and input label + titleL.setText("Save configuration as:"); + inputL.setText("Name:"); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + String configName; + + // Save of the configuration + source = e.getSource(); + if (source == acceptB) + { + configName = getInput(); + refFrontend.save(configName); + } + + // Call parent behavior + super.actionPerformed(e); + } + + @Override + protected void updateGui() + { + String inputStr, infoMsg; + boolean isEnabled; + + // Assume the GUI is invalid + isEnabled = false; + + // Retrieve the name + inputStr = inputTF.getText(); + + // Determine the validity of specified name + if (inputStr.equals("") == true) + { + infoMsg = "Please enter a valid configuration name."; + } + else if (inputStr.startsWith(".") == true) + { + infoMsg = "Invalid name. Name can not start with the char '.'"; + } + else if (refFrontend.getSetting(inputStr) != null) + { + infoMsg = "Name in use. Configuration will be overwritten."; + isEnabled = true; + } + else + { + infoMsg = ""; + isEnabled = true; + } + + infoL.setText(infoMsg); + acceptB.setEnabled(isEnabled); + } + +} diff --git a/src/glum/gui/dock/FrontendManageConfigPanel.java b/src/glum/gui/dock/FrontendManageConfigPanel.java new file mode 100644 index 0000000..3d6679a --- /dev/null +++ b/src/glum/gui/dock/FrontendManageConfigPanel.java @@ -0,0 +1,252 @@ +package glum.gui.dock; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.border.BevelBorder; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +//import echo.gui.LookUp; + +import com.google.common.collect.Lists; + +import glum.database.QueryItem; +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.panel.GlassPanel; +import glum.gui.panel.itemList.ItemListPanel; +import glum.gui.panel.itemList.StaticItemProcessor; +import glum.gui.panel.itemList.query.QueryComposer; +import glum.gui.panel.itemList.query.QueryItemHandler; + +import bibliothek.gui.DockFrontend; + +import net.miginfocom.swing.MigLayout; + +public class FrontendManageConfigPanel extends GlassPanel implements ActionListener, ListSelectionListener +{ + // GUI vars + protected ItemListPanel listPanel; + protected JButton deleteB, closeB; + + // State vars + protected DockFrontend refFrontend; + protected StaticItemProcessor myItemProcessor; + + /** + * Constructor + * @param aFrontend + */ + public FrontendManageConfigPanel(Component aParent, DockFrontend aFrontend) + { + super(aParent); + + // State vars + refFrontend = aFrontend; + + // Build the actual GUI + buildGuiArea(); + setPreferredSize(new Dimension(250, 300));//TODO:getPreferredSize().height)); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(closeB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(closeB)); + FocusUtil.addFocusKeyBinding(listPanel.getTable(), "ENTER", new ClickAction(closeB)); + } + + @Override + public void setVisible(boolean aBool) + { + resetGui(); + + super.setVisible(aBool); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + ConfigItem chosenItem; + + chosenItem = listPanel.getSelectedItem(); + + source = e.getSource(); + if (source == deleteB) + { + refFrontend.delete(chosenItem.getName()); + resetGui(); + } + else if (source == closeB) + { + setVisible(false); + } + } + + @Override + public void valueChanged(ListSelectionEvent aEvent) + { + if (aEvent.getValueIsAdjusting() == true) + return; + + updateGui(); + } + + /** + * buildGuiArea - Forms the actual dialog GUI + */ + private void buildGuiArea() + { + JLabel tmpL; + QueryItemHandler aItemHandler; + QueryComposer aComposer; + + + setLayout(new MigLayout("", "[grow][][]", "[][grow][]")); + + tmpL = new JLabel("Select configuration:", JLabel.CENTER); + add(tmpL, "growx,span 4,wrap"); + + // Construct the actual table panel + aComposer = new QueryComposer(); + aComposer.addAttribute(LookUp.Name, String.class, "Configuration", null); + + myItemProcessor = new StaticItemProcessor(); + aItemHandler = new QueryItemHandler(aComposer); + listPanel = new ItemListPanel(aItemHandler, myItemProcessor, false, true); + listPanel.addListSelectionListener(this); + add(listPanel, "growx,growy,span 4,wrap"); + + // Control area + deleteB = GuiUtil.createJButton("Delete", this); + closeB = GuiUtil.createJButton("Close", this); + + add(deleteB, "skip 1,span 1"); + add(closeB, "span 1"); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + /** + * Utility method to update item list + */ + private void resetGui() + { + Collection strList; + Collection itemList; + + itemList = Lists.newLinkedList(); + + strList = refFrontend.getSettings(); + for (String aStr : strList) + { + // Add only non reserved items + if (aStr.charAt(0) != '.') + itemList.add(new ConfigItem(aStr)); + } + + myItemProcessor.setItems(itemList); + + // TODO: Ugly code: Should be able to just call updateGui but can not + SwingUtilities.invokeLater(new Runnable() + { + + @Override + public void run() + { + updateGui(); + } + }); + } + + /** + * Utility method to update the individual gui components + */ + private void updateGui() + { + ConfigItem chosenItem; + boolean isEnabled; + + chosenItem = listPanel.getSelectedItem(); + + if (chosenItem != null) + refFrontend.load(chosenItem.getName()); + + isEnabled = chosenItem != null; + deleteB.setEnabled(isEnabled); + + // Ensure we have the focus. +//listPanel.requestFocusInWindow(); + repaint(); + + // TODO: Ugly code: Not sure why need to request focus multiple times to ensure we regrab + // the focus. This is particularly true when there are multiple DockStations located on different + // windows + SwingUtilities.invokeLater(new Runnable() + { + + @Override + public void run() + { + listPanel.getTable().requestFocus(); + + SwingUtilities.invokeLater(new Runnable() + { + + @Override + public void run() + { + listPanel.getTable().requestFocus(); + } + }); + +// listPanel.getTable().getFocusCycleRootAncestor().setFocusCycleRoot(true); + listPanel.getTable().requestFocusInWindow(); + repaint(); + } + }); + } + + + /** + * Internal class only used to wrap named (string) settings into + * a QueryItem + */ + class ConfigItem implements QueryItem + { + private String refName; + + public ConfigItem(String aName) + { + refName = aName; + } + + /** + * Accessor method + */ + public String getName() + { + return refName; + } + + @Override + public Object getValue(LookUp aLookUp) + { + return refName; + } + + @Override + public void setValue(LookUp aLookUp, Object aObj) + { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/src/glum/gui/dock/LookUp.java b/src/glum/gui/dock/LookUp.java new file mode 100644 index 0000000..838d67d --- /dev/null +++ b/src/glum/gui/dock/LookUp.java @@ -0,0 +1,7 @@ +package glum.gui.dock; + +public enum LookUp +{ + Name + +} diff --git a/src/glum/gui/dock/PlainDockSituationIgnore.java b/src/glum/gui/dock/PlainDockSituationIgnore.java new file mode 100644 index 0000000..3617a25 --- /dev/null +++ b/src/glum/gui/dock/PlainDockSituationIgnore.java @@ -0,0 +1,64 @@ +package glum.gui.dock; + +import java.util.Set; + +import com.google.common.collect.Sets; + +import bibliothek.gui.DockStation; +import bibliothek.gui.dock.DockElement; +import bibliothek.gui.dock.layout.DockSituationIgnore; +import bibliothek.gui.dock.perspective.PerspectiveElement; +import bibliothek.gui.dock.perspective.PerspectiveStation; + +public class PlainDockSituationIgnore implements DockSituationIgnore +{ + private Set> ignoreClassSet; + + public PlainDockSituationIgnore() + { + ignoreClassSet = Sets.newLinkedHashSet(); + } + + /** + * Method to register a new type of Dockable/Station to ignore + */ + public void addIgnoreClass(Class aClass) + { + ignoreClassSet.add(aClass); + } + + @Override + public boolean ignoreElement(PerspectiveElement aElement) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ignoreElement(DockElement aElement) + { + // Check to see if aElement is of one of the types in ignoreClassSet + for (Class aClass : ignoreClassSet) + { + if (aClass.isAssignableFrom(aElement.getClass()) == true) + return true; + } + + return false; + } + + @Override + public boolean ignoreChildren(PerspectiveStation station) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ignoreChildren(DockStation station) + { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/glum/gui/dock/PrimConfig.java b/src/glum/gui/dock/PrimConfig.java new file mode 100644 index 0000000..1dfb05a --- /dev/null +++ b/src/glum/gui/dock/PrimConfig.java @@ -0,0 +1,322 @@ +package glum.gui.dock; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Maps; + +import glum.io.IoUtil; + +public class PrimConfig +{ + // Constants + public static final byte CODE_BOOL = 1; + public static final byte CODE_INT = 10; + public static final byte CODE_LONG = 11; + public static final byte CODE_SHORT = 12; + public static final byte CODE_FLOAT = 13; + public static final byte CODE_DOUBLE = 14; + public static final byte CODE_STRING = 20; + + // State vars + protected Map mySingletonMap; + + /** + * Container which holds a mapping of keys (String) to a bunch + * of primitive objects. + */ + public PrimConfig() + { + mySingletonMap = Maps.newLinkedHashMap(); + } + + /** + * Returns the boolean associated with aKey + * If no such value is found then defaultVal is returned. + */ + public boolean getBoolean(String aKey, boolean defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Boolean.class) + return defaultVal; + + return (Boolean)aValue; + } + + /** + * Returns the int associated with aKey + * If no such value is found then defaultVal is returned. + */ + public int getInt(String aKey, int defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Integer.class) + return defaultVal; + + return (Integer)aValue; + } + + /** + * Returns the long associated with aKey + * If no such value is found then defaultVal is returned. + */ + public long getLong(String aKey, long defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Long.class) + return defaultVal; + + return (Long)aValue; + } + + /** + * Returns the short associated with aKey + * If no such value is found then defaultVal is returned. + */ + public short getShort(String aKey, short defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Short.class) + return defaultVal; + + return (Short)aValue; + } + + /** + * Returns the float associated with aKey + * If no such value is found then defaultVal is returned. + */ + public float getFloat(String aKey, float defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Float.class) + return defaultVal; + + return (Float)aValue; + } + + /** + * Returns the double associated with aKey + * If no such value is found then defaultVal is returned. + */ + public double getDouble(String aKey, double defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != Double.class) + return defaultVal; + + return (Double)aValue; + } + + /** + * Returns the String associated with aKey + * If no such value is found then defaultVal is returned. + */ + public String getString(String aKey, String defaultVal) + { + Object aValue; + + // If no value associated with the key then return defaultVal + aValue = mySingletonMap.get(aKey); + if (aValue == null || aValue.getClass() != String.class) + return defaultVal; + + return (String)aValue; + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setBoolean(String aKey, boolean aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setInt(String aKey, int aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setLong(String aKey, long aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setShort(String aKey, short aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setFloat(String aKey, float aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setDouble(String aKey, double aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Associates aVal with aKey. Note this will overwrite any + * previous association. + */ + public void setString(String aKey, String aValue) + { + mySingletonMap.put(aKey, aValue); + } + + /** + * Utility method to read the configuration from a DataInputStream + */ + public void readBin(DataInputStream aStream) throws IOException + { + int numItems; + String aKey; + byte aType; + + mySingletonMap.clear(); + + numItems = aStream.readInt(); + for (int c1 = 0; c1 < numItems; c1++) + { + aKey = IoUtil.readString(aStream); + aType = aStream.readByte(); + + switch(aType) + { + case CODE_BOOL: + mySingletonMap.put(aKey, aStream.readBoolean()); + break; + + case CODE_INT: + mySingletonMap.put(aKey, aStream.readInt()); + break; + + case CODE_LONG: + mySingletonMap.put(aKey, aStream.readLong()); + break; + + case CODE_SHORT: + mySingletonMap.put(aKey, aStream.readShort()); + break; + + case CODE_FLOAT: + mySingletonMap.put(aKey, aStream.readFloat()); + break; + + case CODE_DOUBLE: + mySingletonMap.put(aKey, aStream.readDouble()); + break; + + case CODE_STRING: + mySingletonMap.put(aKey, IoUtil.readString(aStream)); + break; + + default: + throw new RuntimeException("Unreconnized type: " + aType); + } + } + } + + /** + * Utility method to write the configuration to a DataOutputStream + */ + public void writeBin(DataOutputStream aStream) throws IOException + { + Set keySet; + Object aVal; + + keySet = mySingletonMap.keySet(); + + aStream.writeInt(keySet.size()); + for (String aKey : keySet) + { + IoUtil.writeString(aStream, aKey); + + aVal = mySingletonMap.get(aKey); + if (aVal instanceof Boolean) + { + aStream.writeByte(CODE_BOOL); + aStream.writeBoolean((Boolean)aVal); + } + else if (aVal instanceof Integer) + { + aStream.writeByte(CODE_INT); + aStream.writeInt((Integer)aVal); + } + else if (aVal instanceof Long) + { + aStream.writeByte(CODE_LONG); + aStream.writeLong((Long)aVal); + } + else if (aVal instanceof Short) + { + aStream.writeByte(CODE_SHORT); + aStream.writeLong((Short)aVal); + } + else if (aVal instanceof Float) + { + aStream.writeByte(CODE_FLOAT); + aStream.writeFloat((Float)aVal); + } + else if (aVal instanceof Double) + { + aStream.writeByte(CODE_DOUBLE); + aStream.writeDouble((Double)aVal); + } + else if (aVal instanceof String) + { + aStream.writeByte(CODE_STRING); + IoUtil.writeString(aStream, (String)aVal); + } + else + { + throw new RuntimeException("Unsupported Object: " + aVal); + } + } + } + +} diff --git a/src/glum/gui/dock/PrimDock.java b/src/glum/gui/dock/PrimDock.java new file mode 100644 index 0000000..ada6192 --- /dev/null +++ b/src/glum/gui/dock/PrimDock.java @@ -0,0 +1,35 @@ +package glum.gui.dock; + +import bibliothek.gui.dock.DockElement; + +/** + * Base class for Dockables which would like to store their configuration via + * the PrimConfig mechanism. Note if the child class will be loaded with the + * PrimDockableFactory, then you should have a constructor with one of the + * following properties:. + *

  • 1 arguments: Registry + *
  • 0 arguments: Empty Constructor + *
    + *
    It is also important the method getFactoryID() is overridden so that it returns PrimDockableFactory.ID + */ +public interface PrimDock extends DockElement +{ + /** + * Returns the PrimConfig associated with the Dockable + * @return + */ + public abstract PrimConfig getConfiguration(); + + /** + * Configures the Dockable with the aConfig + * @return + */ + public abstract void setConfiguration(PrimConfig aConfig); + +// @Override +// public String getFactoryID() +// { +// return PrimDockableFactory.ID; +// } + +} diff --git a/src/glum/gui/dock/PrimDockFactory.java b/src/glum/gui/dock/PrimDockFactory.java new file mode 100644 index 0000000..434c942 --- /dev/null +++ b/src/glum/gui/dock/PrimDockFactory.java @@ -0,0 +1,197 @@ +package glum.gui.dock; + +import glum.reflect.ReflectUtil; +import glum.registry.Registry; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.Map; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.DockFactory; +import bibliothek.gui.dock.dockable.DefaultDockablePerspective; +import bibliothek.gui.dock.layout.LocationEstimationMap; +import bibliothek.gui.dock.perspective.PerspectiveDockable; +import bibliothek.gui.dock.station.support.PlaceholderStrategy; +import bibliothek.util.xml.XElement; + +/** + * Generic DockableFactory for creating PrimDocks. + *

    + * Note that before this factory is used all PrimDockable class types must first be associated with a spawnName. This is + * used during serialization configuration associated with PrimDock. See method {@link PrimDockFactory#addSpawnMapping} + */ +public class PrimDockFactory implements DockFactory +{ + // Constants + public static final String ID = "PrimDockFactory"; + public static final String SpawnNameKey = "factory.spawnName"; + + // State var + protected Registry refRegistry; + protected BiMap> spawnMap; + + public PrimDockFactory(Registry aRegistry) + { + refRegistry = aRegistry; + spawnMap = HashBiMap.create(); + } + + /** + * Add a mapping for a PrimDockable to a the associated spawnName. It is mandatory + * that this mapping is always the same regardless of application executions, as + * this value will be serialized to the disk. + */ + public void addSpawnMapping(String spawnName, Class spawnClass) + { + // Ensure the spawnName is not already reserved + if (spawnMap.containsKey(spawnName) == true) + throw new RuntimeException("Previous mapping stored for spawnName:" + spawnName); + + // Ensure the spawnClass is not already stored + if (spawnMap.inverse().containsKey(spawnClass) == true) + throw new RuntimeException("Previous mapping stored for spawnClass:" + spawnClass); + + spawnMap.put(spawnName, spawnClass); + } + + + @Override + public String getID() + { + return ID; + } + + @Override + public PrimConfig getLayout(PrimDock aDockable, Map children) + { + PrimConfig rConfig; + String spawnName; + + rConfig = aDockable.getConfiguration(); + + // Store the associated spawnName used to instantiate the Dockable + spawnName = spawnMap.inverse().get(aDockable.getClass()); + if (spawnName == null) + throw new RuntimeException("Factory is not configured properly. Failed to locate associated spawnName for class:" + aDockable.getClass()); + + // Ensure that the SpawnNameKey is not already reserved. + ; // TODO + + rConfig.setString(SpawnNameKey, spawnName); + return rConfig; + } + + @Override + public PrimConfig getPerspectiveLayout(DefaultDockablePerspective element, Map children) + { + // Perspectives are not supported + return null; + } + + @Override + public void setLayout(PrimDock aDockable, PrimConfig aLayout, Map children, PlaceholderStrategy placeholders) + { + aDockable.setConfiguration(aLayout); + } + + @Override + public void setLayout(PrimDock aDockable, PrimConfig aLayout, PlaceholderStrategy placeholders) + { + aDockable.setConfiguration(aLayout); + } + + @Override + public void write(PrimConfig aLayout, DataOutputStream aStream) throws IOException + { + aLayout.writeBin(aStream); + } + + @Override + public void write(PrimConfig layout, XElement element) + { + throw new UnsupportedOperationException(); + } + + @Override + public PrimConfig read(DataInputStream aStream, PlaceholderStrategy placeholders) throws IOException + { + PrimConfig rLayout; + + rLayout = new PrimConfig(); + rLayout.readBin(aStream); + return rLayout; + } + + @Override + public PrimConfig read(XElement element, PlaceholderStrategy placeholders) + { + throw new UnsupportedOperationException(); + } + + @Override + public void estimateLocations(PrimConfig layout, LocationEstimationMap children) + { + ; // Nothing to do + } + + @Override + public PrimDock layout(PrimConfig layout, Map children, PlaceholderStrategy placeholders) + { + PrimDock aDockable; + + aDockable = layout(layout, placeholders); + return aDockable; + } + + @Override + public PrimDock layout(PrimConfig aLayout, PlaceholderStrategy placeholders) + { + PrimDock rDockable; + Class spawnClass; + ConstructorspawnConstructor; + Class parmTypes[] = {Registry.class}; + Object parmValues[] = {refRegistry}; + String spawnName; + + spawnName = aLayout.getString(SpawnNameKey, null); + + spawnClass = spawnMap.get(spawnName); + if (spawnClass == null) + throw new RuntimeException("Factory is not configured properly. Failed to locate associated class for spawn name:" + spawnName); + + try + { + spawnConstructor = ReflectUtil.getConstructorSafe(spawnClass, parmTypes); + if (spawnConstructor != null) + rDockable = spawnConstructor.newInstance(parmValues); + else + rDockable = spawnClass.newInstance(); + } + catch (Exception aExp) + { + throw new RuntimeException("Failed to instantite class.", aExp); + } + + rDockable.setConfiguration(aLayout); + return rDockable; + } + + @Override + public DefaultDockablePerspective layoutPerspective(PrimConfig layout, Map children) + { + // Perspectives are not supported + return null; + } + + @Override + public void layoutPerspective(DefaultDockablePerspective perspective, PrimConfig layout, Map children) + { + ; // Nothing to do + } + +} diff --git a/src/glum/gui/dock/PrimDockable.java b/src/glum/gui/dock/PrimDockable.java new file mode 100644 index 0000000..5e37117 --- /dev/null +++ b/src/glum/gui/dock/PrimDockable.java @@ -0,0 +1,27 @@ +package glum.gui.dock; + +import bibliothek.gui.dock.DefaultDockable; + +/** + * Base class for Dockables which would like to store their configuration via + * the PrimConfig mechanism. Note if the child class will be loaded with the + * PrimDockableFactory, then you should have a constructor with one of the + * following properties:. + *

  • 1 arguments: Registry + *
  • 0 arguments: Empty Constructor + */ +public abstract class PrimDockable extends DefaultDockable implements PrimDock +{ + @Override + public abstract PrimConfig getConfiguration(); + + @Override + public abstract void setConfiguration(PrimConfig aConfig); + + @Override + public String getFactoryID() + { + return PrimDockFactory.ID; + } + +} diff --git a/src/glum/gui/dock/action/Closeable.java b/src/glum/gui/dock/action/Closeable.java new file mode 100644 index 0000000..c1995dc --- /dev/null +++ b/src/glum/gui/dock/action/Closeable.java @@ -0,0 +1,10 @@ +package glum.gui.dock.action; + +public interface Closeable +{ + /** + * Notification method sent when this object should be closed + */ + public void close(); + +} \ No newline at end of file diff --git a/src/glum/gui/dock/action/Destroyable.java b/src/glum/gui/dock/action/Destroyable.java new file mode 100644 index 0000000..7acc10b --- /dev/null +++ b/src/glum/gui/dock/action/Destroyable.java @@ -0,0 +1,10 @@ +package glum.gui.dock.action; + +public interface Destroyable +{ + /** + * Notification method sent when this object should be destroyed and all associated references should be released. + */ + public void destroy(); + +} diff --git a/src/glum/gui/dock/action/DismissAction.java b/src/glum/gui/dock/action/DismissAction.java new file mode 100644 index 0000000..991a87e --- /dev/null +++ b/src/glum/gui/dock/action/DismissAction.java @@ -0,0 +1,46 @@ +package glum.gui.dock.action; + +import javax.swing.Icon; + +import bibliothek.gui.DockStation; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.action.actions.SimpleButtonAction; + +/** + * A DockAction that will dismiss the targeted Dockable. If the targeted Dockable is a Destroyable, then is's destroy + * method will be called. If the targeted Dockable is a Closeable then it's close method is called. As a last resort the + * Dockable will just be hidden by removing it from it's parent. This should effectively hide the Dockable. + */ +public class DismissAction extends SimpleButtonAction +{ + public DismissAction(String aText, Icon aIcon) + { + setText(aText); + setIcon(aIcon); + } + + @Override + public void action(Dockable aDockable) + { + DockStation aStation; + + super.action(aDockable); + + // Destroy the Destroyable + if (aDockable instanceof Destroyable) + { + ((Destroyable)aDockable).destroy(); + } + // Close the Closable + else if (aDockable instanceof Closeable) + { + ((Closeable)aDockable).close(); + } + // Remove the Dockable from it's parent (last resort) + else + { + aStation = aDockable.getDockParent(); + aStation.drag(aDockable); + } + } +} diff --git a/src/glum/gui/dock/action/MakeVisibleAction.java b/src/glum/gui/dock/action/MakeVisibleAction.java new file mode 100644 index 0000000..f40dc08 --- /dev/null +++ b/src/glum/gui/dock/action/MakeVisibleAction.java @@ -0,0 +1,33 @@ +package glum.gui.dock.action; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.action.actions.SimpleButtonAction; + +/** + * A DockAction that will cause the target component to become visible. This action is useful to bring up a + * configuration panel related to the Dockable. + */ +public class MakeVisibleAction extends SimpleButtonAction +{ + private JComponent targComp; + + public MakeVisibleAction(JComponent aTargetComp, String aText, Icon aIcon) + { + targComp = aTargetComp; + + setText(aText); + setIcon(aIcon); + } + + @Override + public void action(Dockable aDockable) + { + super.action(aDockable); + + // Make the component visible + targComp.setVisible(true); + } +} diff --git a/src/glum/gui/dock/action/SimpleDockAction.java b/src/glum/gui/dock/action/SimpleDockAction.java new file mode 100644 index 0000000..d75e134 --- /dev/null +++ b/src/glum/gui/dock/action/SimpleDockAction.java @@ -0,0 +1,33 @@ +package glum.gui.dock.action; + +import java.awt.event.ActionEvent; + +import javax.swing.Action; +import javax.swing.Icon; + +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.action.actions.SimpleButtonAction; + +/** + * An DockAction that will fire trigger an embedded java.awt Action + */ +public class SimpleDockAction extends SimpleButtonAction +{ + protected Action refAction; + + public SimpleDockAction(Action aAction, String aText, Icon aIcon) + { + refAction = aAction; + + setText(aText); + setIcon(aIcon); + } + + @Override + public void action(Dockable dockable) + { + super.action(dockable); + + refAction.actionPerformed(new ActionEvent(this, 0, "SimpleDockAction")); + } +} diff --git a/src/glum/gui/dock/action/ToggleAction.java b/src/glum/gui/dock/action/ToggleAction.java new file mode 100644 index 0000000..eed2ec1 --- /dev/null +++ b/src/glum/gui/dock/action/ToggleAction.java @@ -0,0 +1,66 @@ +package glum.gui.dock.action; + +import javax.swing.Icon; + +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.action.actions.SimpleButtonAction; + +/** + * An DockAction that will fire trigger an embedded java.awt Action + */ +public class ToggleAction extends SimpleButtonAction +{ + // State vars + protected boolean isActive; + + // Gui vars + protected Icon falseIcon, trueIcon; + + public ToggleAction(String aText, Icon aFalseIcon, Icon aTrueIcon, boolean aIsActive) + { + super(); + + isActive = aIsActive; + + falseIcon = aFalseIcon; + trueIcon = aTrueIcon; + + setText(aText); + updateGui(); + } + + /** + * Accessor methods + */ + public boolean getIsActive() + { + return isActive; + } + + public void setIsActive(boolean aBool) + { + isActive = aBool; + updateGui(); + } + + @Override + public void action(Dockable aDockable) + { + isActive = !isActive; + updateGui(); + + super.action(aDockable); + } + + /** + * Utility method + */ + private void updateGui() + { + if (isActive == true) + setIcon(trueIcon); + else + setIcon(falseIcon); + } + +} diff --git a/src/glum/gui/dock/alt/AltScreenDockFrame.java b/src/glum/gui/dock/alt/AltScreenDockFrame.java new file mode 100644 index 0000000..e161d2f --- /dev/null +++ b/src/glum/gui/dock/alt/AltScreenDockFrame.java @@ -0,0 +1,105 @@ +package glum.gui.dock.alt; + +import glum.gui.dock.action.Destroyable; + +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import javax.swing.JDialog; +import javax.swing.JFrame; + +import bibliothek.gui.DockFrontend; +import bibliothek.gui.DockStation; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.ScreenDockStation; +import bibliothek.gui.dock.station.screen.ScreenDockFrame; + +public class AltScreenDockFrame extends ScreenDockFrame implements WindowListener +{ + private DockFrontend refFrontend; + private JFrame frame; + + /** + * Creates a new ScroonDockWindow with an associated JFrame. + * + * @param aStation + * the station to which this dialog is responsible + * @param isUndecorated + * whether the dialog should suppress the default decorations + */ + public AltScreenDockFrame(DockFrontend aFrontend, ScreenDockStation aStation, boolean isUndecorated) + { + super(aStation, isUndecorated); + + refFrontend = aFrontend; + + // Set up the Jframe + frame = getFrame(); + frame.addWindowListener(this); + frame.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + } + + @Override + public void windowOpened(WindowEvent e) + { + ; // Nothing to do + } + + @Override + public void windowClosing(WindowEvent e) + { + Dockable dockable; + + System.out.println("Window should be closed...."); + dockable = getDockable(); + + // Destroy the associated Destroyable + if (dockable instanceof Destroyable) + { + ((Destroyable)dockable).destroy(); + } + // Attempt to hide the (if it is registered) Dockable + else if (refFrontend != null && refFrontend.listDockables().contains(dockable) == true) + { + refFrontend.hide(dockable); + } + // Just hide the dockable + else + { + DockStation aStation; + + aStation = dockable.getDockParent(); + aStation.drag(dockable); + } + } + + @Override + public void windowClosed(WindowEvent e) + { + ; // Nothing to do + } + + @Override + public void windowIconified(WindowEvent e) + { + ; // Nothing to do + } + + @Override + public void windowDeiconified(WindowEvent e) + { + ; // Nothing to do + } + + @Override + public void windowActivated(WindowEvent e) + { + ; // Nothing to do + } + + @Override + public void windowDeactivated(WindowEvent e) + { + ; // Nothing to do + } + +} diff --git a/src/glum/gui/dock/alt/AltScreenDockStation.java b/src/glum/gui/dock/alt/AltScreenDockStation.java new file mode 100644 index 0000000..a46b25a --- /dev/null +++ b/src/glum/gui/dock/alt/AltScreenDockStation.java @@ -0,0 +1,117 @@ +package glum.gui.dock.alt; + +import glum.gui.dock.BaseDockable; + +import java.awt.Window; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.ScreenDockStation; +import bibliothek.gui.dock.action.DefaultDockActionSource; +import bibliothek.gui.dock.action.DockAction; +import bibliothek.gui.dock.action.DockActionSource; +import bibliothek.gui.dock.action.LocationHint; + +/** + * Alternative ScreenDockStation which provides no default direct/indirect action offers. + */ +public class AltScreenDockStation extends ScreenDockStation +{ + // Action vars + private List directDockActionList; + private List indirectDockActionList; + + // Lock vars + private Set lockSet; + private boolean isLocked; + + public AltScreenDockStation(Window owner) + { + super(owner); + + directDockActionList = Lists.newArrayList(); + indirectDockActionList = Lists.newArrayList(); + + lockSet = Sets.newHashSet(); + isLocked = false; + } + + /** + * Registers a DockAction to always be available for direct Dockables + */ + public void addDirectActionOffer(DockAction aDockAction) + { + directDockActionList.add(aDockAction); + } + + /** + * Registers a DockAction to always be available for indirect Dockables + */ + public void addIndirectActionOffer(DockAction aDockAction) + { + indirectDockActionList.add(aDockAction); + } + + /** + * Utility method to force this station to accept no further Dockables. Only dockables that are currently children + * will be accepted. + */ + public void lockStation(boolean aBool) + { + isLocked = aBool; + + if (isLocked == false) + { + lockSet.clear(); + return; + } + + // Record all of the valid children when the lock is triggered + for (int c1 = 0; c1 < getDockableCount(); c1++) + { + lockSet.add(getDockable(c1)); + + } + } + + @Override + public boolean accept(Dockable aChild) + { + // If we are locked then never accept any Dockable, which was not recorded as valid when the lock happened + if (isLocked == true && lockSet.contains(aChild) == false) + return false; + + // Never accept any Dockable that has been marked as nontransferable + if (aChild instanceof BaseDockable) + return ((BaseDockable)aChild).isTransferable(this); + + return super.accept(aChild); + } + + @Override + public DefaultDockActionSource getDirectActionOffers(Dockable dockable) + { + DefaultDockActionSource source; + + source = new DefaultDockActionSource(new LocationHint(LocationHint.DIRECT_ACTION, LocationHint.VERY_RIGHT)); + source.add(directDockActionList.toArray(new DockAction[0])); + + return source; + } + + @Override + public DockActionSource getIndirectActionOffers(Dockable dockable) + { + DefaultDockActionSource source; + + source = new DefaultDockActionSource(new LocationHint(LocationHint.INDIRECT_ACTION, LocationHint.VERY_RIGHT)); + source.add(indirectDockActionList.toArray(new DockAction[0])); + + return source; + } + +} diff --git a/src/glum/gui/dock/alt/AltScreenDockWindowFactory.java b/src/glum/gui/dock/alt/AltScreenDockWindowFactory.java new file mode 100644 index 0000000..6c0aba7 --- /dev/null +++ b/src/glum/gui/dock/alt/AltScreenDockWindowFactory.java @@ -0,0 +1,69 @@ +package glum.gui.dock.alt; + +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.Window; + +import javax.swing.Icon; + +import bibliothek.gui.DockFrontend; +import bibliothek.gui.dock.ScreenDockStation; +import bibliothek.gui.dock.station.screen.AbstractScreenDockWindow; +import bibliothek.gui.dock.station.screen.DefaultScreenDockWindowFactory; +import bibliothek.gui.dock.station.screen.ScreenDockDialog; +import bibliothek.gui.dock.station.screen.ScreenDockWindow; + +/** + * Alternative ScreenDockWindowFactory that return AltScreenDockFrame instead of DefaultScreenDockFrame. Also the + * returned windows will by default have the typical OS decorations. + */ +public class AltScreenDockWindowFactory extends DefaultScreenDockWindowFactory +{ + private DockFrontend refFrontend; + + public AltScreenDockWindowFactory(DockFrontend aFrontend) + { + refFrontend = aFrontend; + + setKind(Kind.FRAME); + setUndecorated(false); + } + + @Override + public ScreenDockWindow createWindow(ScreenDockStation station) + { + Kind kind; + boolean undecorated; + boolean showDockTitle; + Icon titleIcon; + String titleText; + + kind = getKind(); + undecorated = isUndecorated(); + showDockTitle = isShowDockTitle(); + titleIcon = getTitleIcon(); + titleText = getTitleText(); + + AbstractScreenDockWindow window; + + if (kind == Kind.FRAME) + { + window = new AltScreenDockFrame(refFrontend, station, undecorated); + } + else + { + Window owner = station.getOwner(); + if (owner instanceof Frame) + window = new ScreenDockDialog(station, (Frame)owner, undecorated); + else if (owner instanceof Dialog) + window = new ScreenDockDialog(station, (Dialog)owner, undecorated); + else + window = new ScreenDockDialog(station, undecorated); + } + + window.setShowTitle(showDockTitle); + window.setTitleIcon(titleIcon); + window.setTitleText(titleText); + return window; + } +} diff --git a/src/glum/gui/dock/alt/AltSplitDockStation.java b/src/glum/gui/dock/alt/AltSplitDockStation.java new file mode 100644 index 0000000..c1e3e0b --- /dev/null +++ b/src/glum/gui/dock/alt/AltSplitDockStation.java @@ -0,0 +1,176 @@ +package glum.gui.dock.alt; + +import glum.gui.dock.BaseDockable; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.SplitDockStation; +import bibliothek.gui.dock.action.DefaultDockActionSource; +import bibliothek.gui.dock.action.DockAction; +import bibliothek.gui.dock.action.DockActionSource; +import bibliothek.gui.dock.action.LocationHint; + +public class AltSplitDockStation extends SplitDockStation +{ + // Action vars + private List directDockActionList; + private List localDockActionList; + private List indirectDockActionList; + + // Lock vars + private Set lockSet; + private boolean isLocked; + + public AltSplitDockStation() + { + super(); + + directDockActionList = Lists.newArrayList(); + localDockActionList = Lists.newArrayList(); + indirectDockActionList = Lists.newArrayList(); + + lockSet = Sets.newHashSet(); + isLocked = false; + } + + /** + * Registers a DockAction to always be available for direct Dockables + */ + public void addDirectActionOffer(DockAction aDockAction) + { + directDockActionList.add(aDockAction); + } + + /** + * Registers a DockAction to always be available for local Dockables + */ + public void addLocalActionOffer(DockAction aDockAction) + { + localDockActionList.add(aDockAction); + } + + /** + * Registers a DockAction to always be available for indirect Dockables + */ + public void addIndirectActionOffer(DockAction aDockAction) + { + indirectDockActionList.add(aDockAction); + } + + /** + * Utility method to force this station to accept no further Dockables. Only dockables that are currently children + * will be accepted. + */ + public void lockStation(boolean aBool) + { + isLocked = aBool; + + if (isLocked == false) + { + lockSet.clear(); + return; + } + + // Record all of the valid children when the lock is triggered + for (int c1 = 0; c1 < getDockableCount(); c1++) + { + lockSet.add(getDockable(c1)); + + } + } + + @Override + public boolean accept(Dockable aChild) + { + // If we are locked then never accept any Dockable, which was not recorded as valid when the lock happened + if (isLocked == true && lockSet.contains(aChild) == false) + return false; + + // Never accept any Dockable that has been marked as nontransferable + if (aChild instanceof BaseDockable) + return ((BaseDockable)aChild).isTransferable(this); + + // Default behavior for non BaseDockables + return super.accept(aChild); + } + +// +// @Override +// protected boolean acceptable(Dockable old, Dockable next) +// { +// return false; +//// // Never accept any Dockable that has been marked as nontransferable +//// if (old instanceof BaseDockable || next instanceof BaseDockable) +//// { +//// if (((BaseDockable)old).isTransferable() == false) +//// return false; +//// +//// if (((BaseDockable)next).isTransferable() == false) +//// return false; +//// } +//// +//// +//// // TODO Auto-generated method stub +//// return super.acceptable(old, next); +// } + +// @Override +// public boolean canDrag(Dockable aDockable) +// { +// if (lockSet.contains(aDockable) == true) +// return false; +// +// return super.canDrag(aDockable); +// } +// +// +// @Override +// public boolean canReplace(Dockable oldDockable, Dockable nextDockable) +// { +// if (lockSet.contains(oldDockable) == true) +// return false; +// +// if (lockSet.contains(nextDockable) == true) +// return false; +// +// return super.canReplace(oldDockable, nextDockable); +// } + + @Override + public DefaultDockActionSource getDirectActionOffers(Dockable dockable) + { + DefaultDockActionSource source; + + source = new DefaultDockActionSource(new LocationHint(LocationHint.DIRECT_ACTION, LocationHint.VERY_RIGHT)); + source.add(directDockActionList.toArray(new DockAction[0])); + + return source; + } + + @Override + public DockActionSource getLocalActionOffers() + { + DefaultDockActionSource source; + + source = new DefaultDockActionSource(new LocationHint(LocationHint.DOCKABLE, LocationHint.RIGHT)); + source.add(localDockActionList.toArray(new DockAction[0])); + + return source; + } + + @Override + public DockActionSource getIndirectActionOffers(Dockable dockable) + { + DefaultDockActionSource source; + + source = new DefaultDockActionSource(new LocationHint(LocationHint.INDIRECT_ACTION, LocationHint.VERY_RIGHT)); + source.add(indirectDockActionList.toArray(new DockAction[0])); + + return source; + } + +} diff --git a/src/glum/gui/document/BaseDocument.java b/src/glum/gui/document/BaseDocument.java new file mode 100644 index 0000000..64926aa --- /dev/null +++ b/src/glum/gui/document/BaseDocument.java @@ -0,0 +1,50 @@ +package glum.gui.document; + +import java.awt.event.*; +import javax.swing.text.*; +import javax.swing.JTextField; + +public abstract class BaseDocument extends PlainDocument implements ActionListener, FocusListener +{ + // State vars + protected JTextField ownerTF; + + public BaseDocument(JTextField aOwnerTF) + { + super(); + + ownerTF = aOwnerTF; + } + + /** + * Get the owner of this Document model + * Todo: This method should no longer be needed. + */ + public JTextField getOwner() + { + return ownerTF; + } + + @Override + public void actionPerformed(ActionEvent e) + { + formalizeInput(); + } + + @Override + public void focusGained(FocusEvent e) + { + } + + @Override + public void focusLost(FocusEvent e) + { + formalizeInput(); + } + + /** + * Updates the text to reflect a formal output + */ + public abstract void formalizeInput(); + +} diff --git a/src/glum/gui/document/BaseNumberDocument.java b/src/glum/gui/document/BaseNumberDocument.java new file mode 100644 index 0000000..a1973fe --- /dev/null +++ b/src/glum/gui/document/BaseNumberDocument.java @@ -0,0 +1,61 @@ +package glum.gui.document; + +import javax.swing.JTextField; + +public abstract class BaseNumberDocument extends BaseDocument +{ + // State vars + protected double minVal, maxVal; + protected boolean formalizeDoc; + protected int numAvailCols; + + public BaseNumberDocument(JTextField aOwner, double aMinVal, double aMaxVal) + { + super(aOwner); + + setMinMaxValue(aMinVal, aMaxVal); + formalizeDoc = false; + numAvailCols = -1; + } + + /** + * Updates the new range of valid numbers. + */ + public void setMinMaxValue(double aMinVal, double aMaxVal) + { + minVal = aMinVal; + maxVal = aMaxVal; + + // Insanity check + if (minVal >= maxVal) + throw new RuntimeException("Illogical range. Range: [" + minVal + "," + maxVal + "]"); + } + + @Override + public void formalizeInput() + { + String currStr; + + // Insanity check + if (ownerTF == null) + return; + + // Is formalization required + if (formalizeDoc == false) + return; + + // Disassociate ourselves from event handling + ownerTF.removeActionListener(this); + ownerTF.removeFocusListener(this); + + currStr = ownerTF.getText(); + ownerTF.setText(currStr); + + // Reassociate ourselves from event handling + ownerTF.addActionListener(this); + ownerTF.addFocusListener(this); + } + + + +} diff --git a/src/glum/gui/document/CharDocument.java b/src/glum/gui/document/CharDocument.java new file mode 100644 index 0000000..9a1b9d2 --- /dev/null +++ b/src/glum/gui/document/CharDocument.java @@ -0,0 +1,65 @@ +package glum.gui.document; + +import java.util.Set; + +import javax.swing.JTextField; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; + +import com.google.common.collect.Sets; + +/** + * Specialized Document designed to accept only the specified input chars + */ +public class CharDocument extends BaseDocument +{ + private Set validSet; + + public CharDocument(JTextField aOwner, String validCharStr) + { + this(aOwner, validCharStr, true); + } + + public CharDocument(JTextField aOwner, String validCharStr, boolean isCaseSensitive) + { + super(aOwner); + + validSet = Sets.newHashSet(); + for (int c1 = 0; c1 < validCharStr.length(); c1++) + { + validSet.add(validCharStr.charAt(c1)); + if (isCaseSensitive == false) + { + validSet.add(Character.toLowerCase(validCharStr.charAt(c1))); + validSet.add(Character.toUpperCase(validCharStr.charAt(c1))); + } + } + } + + @Override + public void formalizeInput() + { + ; // Nothing to do + } + + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException + { + char aChar; + + // Insanity check + if (str == null) + return; + + // Ensure all of the characters in str are in the valid set + for (int c1 = 0; c1 < str.length(); c1++) + { + aChar = str.charAt(c1); + if (validSet.contains(aChar) == false) + throw new BadLocationException("Invalid character: " + aChar, offs); + } + + super.insertString(offs, str, a); + } + +} diff --git a/src/glum/gui/document/NumberDocument.java b/src/glum/gui/document/NumberDocument.java new file mode 100644 index 0000000..ba17f83 --- /dev/null +++ b/src/glum/gui/document/NumberDocument.java @@ -0,0 +1,109 @@ +package glum.gui.document; + +import javax.swing.text.*; +import javax.swing.JTextField; + +import glum.gui.GuiUtil; + +public class NumberDocument extends BaseNumberDocument +{ + protected boolean allowFloats; +// protected NumberUnit myUnit; + + public NumberDocument(JTextField aOwnerTF, boolean aFormalizeDoc) + { + super(aOwnerTF, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + + allowFloats = true; + + formalizeDoc = aFormalizeDoc; + } + + /** + * Sets in whether floating point numbers are allowed + */ + public void setAllowFloats(boolean aBool) + { + allowFloats = aBool; + } + + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException + { + String bStr, eStr, resultStr; + double aVal; + char aChar; + + // Insanity check + if (str == null) + return; + + // Special cases + aChar = str.charAt(0); + if (offs == 0) + { + // Reject if we need a positive number + if (aChar == '-' && minVal >= 0) + { + throw new BadLocationException("Negative values are not allowed.", offs); + } + + // Potential valid string that starts off an int + if ((aChar == '-') && str.length() == 1) + { + super.insertString(offs, str, a); + return; + } + + // Potential valid string that starts off a double + if ((aChar == '.') && str.length() == 1 && allowFloats == true) + { + super.insertString(offs, str, a); + return; + } + } + else if (offs == 1 && str.length() == 1 && aChar == '.') + { + // Potential valid string that starts off a double + if ("-".equals(getText(0, offs)) == true && allowFloats == true) + { + super.insertString(offs, str, a); + return; + } + } + + // Reject if we detect a floating point, but it is not allowed + if (str.contains(".") == true && allowFloats == false) + throw new BadLocationException("Only integers are allowed.", offs); + + // Ensure we do not exceed number of columns + if (numAvailCols > 0) + { + if (offs + str.length() >= numAvailCols) + throw new BadLocationException("Too many characters to insert.", offs); + } + + // Form the resultant string + bStr = ""; + eStr = ""; + if (offs > 0) + bStr = getText(0, offs); + eStr = getText(offs, getLength() - offs); + resultStr = bStr + str + eStr; + + // Ensure the resultant is sensical + aVal = GuiUtil.readDouble(resultStr, Double.NaN); + if (Double.isNaN(aVal) == true) + throw new BadLocationException("Nonsensical number.", offs); + + // Ensure that the number is in range + if (aVal > maxVal) + throw new BadLocationException("Out of numerical range.", offs); + else if (aVal < minVal) + throw new BadLocationException("Out of numerical range.", offs); + + // Insert the string + super.insertString(offs, str, a); + } + +} diff --git a/src/glum/gui/icon/ArrowNorthIcon.java b/src/glum/gui/icon/ArrowNorthIcon.java new file mode 100644 index 0000000..7cb2fbe --- /dev/null +++ b/src/glum/gui/icon/ArrowNorthIcon.java @@ -0,0 +1,56 @@ +package glum.gui.icon; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Polygon; +import javax.swing.plaf.metal.MetalLookAndFeel; + +public class ArrowNorthIcon extends BaseIcon +{ + /** + * Constructor + */ + public ArrowNorthIcon(int aDim) + { + super(aDim); + } + + @Override + public void paintIcon(Component aComp, Graphics g, int x, int y) + { + Polygon aPolygon; + int w, h; + int hW, hH; + + w = getIconWidth(); + h = getIconHeight(); + hW = w / 2; + hH = h / 2; + + g.setColor(Color.BLACK); + if (aComp.isEnabled() == false) + g.setColor(MetalLookAndFeel.getControlDisabled()); + + if (IconUtil.isPressed(aComp) == true) + y += 2; + + g.translate(x + hW, y + hH); + + aPolygon = new Polygon(); + aPolygon.addPoint(0, -hH); + aPolygon.addPoint(-hW, 0); + + aPolygon.addPoint(-hW/4, 0); + aPolygon.addPoint(-hW/4, hH); + aPolygon.addPoint(+hW/4, hH); + aPolygon.addPoint(+hW/4, 0); + + aPolygon.addPoint(+hW, 0); + + g.fillPolygon(aPolygon); + + g.translate(-(x + hW), -(y + hH)); + } + +} diff --git a/src/glum/gui/icon/ArrowSouthIcon.java b/src/glum/gui/icon/ArrowSouthIcon.java new file mode 100644 index 0000000..b275f74 --- /dev/null +++ b/src/glum/gui/icon/ArrowSouthIcon.java @@ -0,0 +1,56 @@ +package glum.gui.icon; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Polygon; +import javax.swing.plaf.metal.MetalLookAndFeel; + +public class ArrowSouthIcon extends BaseIcon +{ + /** + * Constructor + */ + public ArrowSouthIcon(int aDim) + { + super(aDim); + } + + @Override + public void paintIcon(Component aComp, Graphics g, int x, int y) + { + Polygon aPolygon; + int w, h; + int hW, hH; + + w = getIconWidth(); + h = getIconHeight(); + hW = w / 2; + hH = h / 2; + + g.setColor(Color.BLACK); + if (aComp.isEnabled() == false) + g.setColor(MetalLookAndFeel.getControlDisabled()); + + if (IconUtil.isPressed(aComp) == true) + y += 2; + + g.translate(x + hW, y + hH); + + aPolygon = new Polygon(); + aPolygon.addPoint(0, +hH); + aPolygon.addPoint(+hW, 0); + + aPolygon.addPoint(+hW/4, 0); + aPolygon.addPoint(+hW/4, -hH); + aPolygon.addPoint(-hW/4, -hH); + aPolygon.addPoint(-hW/4, 0); + + aPolygon.addPoint(-hW, 0); + + g.fillPolygon(aPolygon); + + g.translate(-(x + hW), -(y + hH)); + } + +} diff --git a/src/glum/gui/icon/BaseIcon.java b/src/glum/gui/icon/BaseIcon.java new file mode 100644 index 0000000..e6b69db --- /dev/null +++ b/src/glum/gui/icon/BaseIcon.java @@ -0,0 +1,29 @@ +package glum.gui.icon; + +import javax.swing.Icon; + +public abstract class BaseIcon implements Icon +{ + protected int mDim; + + /** + * Constructor + */ + public BaseIcon(int aDim) + { + mDim = aDim; + } + + @Override + public int getIconWidth() + { + return mDim; + } + + @Override + public int getIconHeight() + { + return mDim; + } + +} diff --git a/src/glum/gui/icon/DeleteIcon.java b/src/glum/gui/icon/DeleteIcon.java new file mode 100644 index 0000000..99a39d5 --- /dev/null +++ b/src/glum/gui/icon/DeleteIcon.java @@ -0,0 +1,58 @@ +package glum.gui.icon; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import javax.swing.plaf.metal.MetalLookAndFeel; + +public class DeleteIcon extends BaseIcon +{ + public DeleteIcon(int aDim) + { + super(aDim); + } + + @Override + public void paintIcon(Component aComp, Graphics g, int x, int y) + { + Graphics2D g2d; + BasicStroke aStroke; + int w, h; + int hW, hH, dX, dY; + int shrinkSize; + float strokeW; + + w = getIconWidth(); + h = getIconHeight(); + hW = w / 2; + hH = h / 2; + + g2d = (Graphics2D)g; + + g2d.setColor(Color.RED.darker()); + if (aComp.isEnabled() == false) + g2d.setColor(MetalLookAndFeel.getControlDisabled()); + + shrinkSize = 2; + if (IconUtil.isPressed(aComp) == true) + shrinkSize += 2; + + g2d.translate(x + hW, y + hH); + + + strokeW = getIconWidth() / 7.0f; + aStroke = new BasicStroke(strokeW, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + + g2d.setStroke(aStroke); + + dX = hW - shrinkSize; + dY = hH - shrinkSize; + g2d.drawLine(-dX,-dY, +dX,+dY); + g2d.drawLine(-dX,+dY, +dX,-dY); + + g2d.translate(-(x + hW), -(y + hH)); + } + +} diff --git a/src/glum/gui/icon/EmptyIcon.java b/src/glum/gui/icon/EmptyIcon.java new file mode 100644 index 0000000..3d29e27 --- /dev/null +++ b/src/glum/gui/icon/EmptyIcon.java @@ -0,0 +1,36 @@ +package glum.gui.icon; + +import java.awt.Component; +import java.awt.Graphics; + +import javax.swing.Icon; + +public class EmptyIcon implements Icon +{ + protected int width, height; + + EmptyIcon(int aWidth, int aHeight) + { + width = aWidth; + height = aHeight; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) + { + ; // Nothing to do + } + + @Override + public int getIconWidth() + { + return width; + } + + @Override + public int getIconHeight() + { + return height; + } + +} diff --git a/src/glum/gui/icon/IconUtil.java b/src/glum/gui/icon/IconUtil.java new file mode 100644 index 0000000..0a5d305 --- /dev/null +++ b/src/glum/gui/icon/IconUtil.java @@ -0,0 +1,35 @@ +package glum.gui.icon; + +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JButton; + +public class IconUtil +{ + /** + * Utility method to load the Icon specified by the resource path. + */ + public static ImageIcon loadIcon(String iconPath) + { +// URL aURL; +// +// aURL = IconUtil.class.getClassLoader().getResource(iconPath); +// if (aURL == null) +// throw new RuntimeException("Failed to load icon for path: " + iconPath); +// +// return new ImageIcon(aURL); + return new ImageIcon(ClassLoader.getSystemResource(iconPath)); + } + + /** + * Utility method to test if aComp is pressed down. + */ + public static boolean isPressed(Component aComp) + { + if (aComp instanceof JButton == false) + return false; + + return ((JButton)aComp).getModel().isPressed(); + } + +} diff --git a/src/glum/gui/info/FilePathInfo.java b/src/glum/gui/info/FilePathInfo.java new file mode 100644 index 0000000..78dc12e --- /dev/null +++ b/src/glum/gui/info/FilePathInfo.java @@ -0,0 +1,40 @@ +package glum.gui.info; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.io.IOException; + +public class FilePathInfo implements ZioRaw +{ + // State vars + protected String filePath; + + public FilePathInfo() + { + filePath = null; + } + + public FilePathInfo(String aFilePath) + { + filePath = aFilePath; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + filePath = aStream.readString(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + aStream.writeVersion(0); + + aStream.writeString(filePath); + } + +} diff --git a/src/glum/gui/info/WindowInfo.java b/src/glum/gui/info/WindowInfo.java new file mode 100644 index 0000000..8c8dbb8 --- /dev/null +++ b/src/glum/gui/info/WindowInfo.java @@ -0,0 +1,79 @@ +package glum.gui.info; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; +import glum.zio.util.ZioUtil; + +import java.awt.*; +import java.io.*; + +public class WindowInfo implements ZioRaw +{ + // Raw vars + protected Point position; + protected Dimension size; + protected boolean isVisible; + + /** + * Constructor + */ + public WindowInfo() + { + position = null; + size = null; + isVisible = false; + } + + public WindowInfo(Component aComponent) + { + this(); + + if (aComponent == null) + return; + + position = aComponent.getLocation(); + size = aComponent.getSize(); + isVisible = aComponent.isVisible(); + } + + /** + * configure - Syncs aComponent with parmaters of this WindowInfo + */ + public void configure(Component aComponent) + { + if (position != null) + aComponent.setLocation(position); + + if (size != null) + { + aComponent.setPreferredSize(size); + aComponent.setSize(size); + } + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + isVisible = aStream.readBool(); + + position = ZioUtil.readPoint(aStream); + + size = ZioUtil.readDimension(aStream); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + aStream.writeVersion(0); + + aStream.writeBool(isVisible); + + ZioUtil.writePoint(aStream, position); + + ZioUtil.writeDimension(aStream, size); + } + +} diff --git a/src/glum/gui/misc/BooleanCellEditor.java b/src/glum/gui/misc/BooleanCellEditor.java new file mode 100644 index 0000000..334bc16 --- /dev/null +++ b/src/glum/gui/misc/BooleanCellEditor.java @@ -0,0 +1,75 @@ +package glum.gui.misc; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.LinkedList; +import javax.swing.AbstractCellEditor; +import javax.swing.JCheckBox; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; + +public class BooleanCellEditor extends AbstractCellEditor implements ActionListener, TableCellEditor +{ + // State vars + protected Collection myListeners; + protected JCheckBox refCheckBox; + + /** + * Constructor + */ + public BooleanCellEditor() + { + this(null); + } + + public BooleanCellEditor(ActionListener aListener) + { + myListeners = new LinkedList(); + if (aListener != null) + myListeners.add(aListener); + + refCheckBox = new JCheckBox("", false); + refCheckBox.addActionListener(this); + } + + public void addActionListener(ActionListener aListener) + { + myListeners.add(aListener); + } + + public void removeActionListener(ActionListener aListener) + { + myListeners.remove(aListener); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + fireEditingStopped(); + + aEvent = new ActionEvent(this, aEvent.getID(), "BooleanCell edited."); + for (ActionListener aListener : myListeners) + aListener.actionPerformed(aEvent); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) + { + // Update our checkbox with the appropriate state + refCheckBox.removeActionListener(this); + if (value instanceof Boolean) + refCheckBox.setSelected((Boolean)value); + refCheckBox.addActionListener(this); + + return refCheckBox; + } + + @Override + public Object getCellEditorValue() + { + return refCheckBox.isSelected(); + } + +} diff --git a/src/glum/gui/misc/BooleanCellRenderer.java b/src/glum/gui/misc/BooleanCellRenderer.java new file mode 100644 index 0000000..30a5062 --- /dev/null +++ b/src/glum/gui/misc/BooleanCellRenderer.java @@ -0,0 +1,28 @@ +package glum.gui.misc; + +import java.awt.Component; + +import javax.swing.JCheckBox; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; + +public class BooleanCellRenderer extends JCheckBox implements TableCellRenderer +{ + public BooleanCellRenderer() + { + super("", false); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + if (value instanceof Boolean) + { + setSelected((Boolean)value); + return this; + } + + return null; + } + +} diff --git a/src/glum/gui/misc/ColorCellRenderer.java b/src/glum/gui/misc/ColorCellRenderer.java new file mode 100644 index 0000000..2d1ede7 --- /dev/null +++ b/src/glum/gui/misc/ColorCellRenderer.java @@ -0,0 +1,60 @@ +package glum.gui.misc; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; + +public class ColorCellRenderer extends JPanel implements TableCellRenderer +{ + // State vars + protected Color activeColor; + + /** + * Constructor + */ + public ColorCellRenderer() + { + super(); + + activeColor = null; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + activeColor = null; + if (value instanceof Color) + activeColor = (Color)value; + + if (activeColor != null) + setBackground(activeColor); + else + setBackground(Color.LIGHT_GRAY); + + return this; + } + + @Override + public void paint(Graphics g) + { + Graphics2D g2d; + + super.paint(g); + g2d = (Graphics2D)g; + + // Bail if we have a valid color + if (activeColor != null) + return; + + // Draw a red x if no valid color + g2d.setColor(Color.RED); + g2d.drawLine(0, 0, getWidth(), getHeight()); + g2d.drawLine(getWidth(), 0, 0, getHeight()); + } + +} diff --git a/src/glum/gui/misc/MultiState.java b/src/glum/gui/misc/MultiState.java new file mode 100644 index 0000000..4161fcd --- /dev/null +++ b/src/glum/gui/misc/MultiState.java @@ -0,0 +1,89 @@ +package glum.gui.misc; + +import java.awt.*; + +public enum MultiState +{ + Checked("Checked", "MultiState.Checked") + { + @Override + public void render(Graphics g, int x, int y, int iconSize) + { + g.fillRect(x + 3, y + 5, 2, iconSize - 8); + g.drawLine(x + (iconSize - 4), y + 3, x + 5, y + (iconSize - 6)); + g.drawLine(x + (iconSize - 4), y + 4, x + 5, y + (iconSize - 5)); + } + }, + + Crossed("Crossed", "MultiState.Crossed") + { + @Override + public void render(Graphics g, int x, int y, int iconSize) + { + g.drawLine(x + (iconSize - 4), y + 2, x + 3, y + (iconSize - 5)); + g.drawLine(x + (iconSize - 4), y + 3, x + 3, y + (iconSize - 4)); + g.drawLine(x + 3, y + 2, x + (iconSize - 4), y + (iconSize - 5)); + g.drawLine(x + 3, y + 3, x + (iconSize - 4), y + (iconSize - 4)); + } + }, + + Mixed("Mixed", "MultiState.Mixed") + { + @Override + public void render(Graphics g, int x, int y, int iconSize) + { + int cX, cY; + + cX = x + iconSize / 2; + cY = y + iconSize / 2; + + g.drawOval(cX - 2, cY - 2, 4, 4); + g.drawOval(cX - 3, cY - 3, 6, 6); + } + }, + + None("None", "MultiState.None") + { + @Override + public void render(Graphics g, int x, int y, int iconSize) + { + ; // Nothing to render + } + }; + + // Vars + private final String userStr; + private final String referenceName; + + /** + * Constructor + */ + MultiState(String aUserStr, String aReferenceName) + { + userStr = aUserStr; + referenceName = aReferenceName; + } + + /** + * getReferenceName + */ + public String getReferenceName() + { + return referenceName; + } + + /** + * Method to draw a representative icon of the state + */ + public void render(Graphics g, int x, int y, int iconSize) + { + this.render(g, x, y, iconSize); + } + + @Override + public String toString() + { + return userStr; + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBox.java b/src/glum/gui/misc/MultiStateCheckBox.java new file mode 100644 index 0000000..428d94e --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBox.java @@ -0,0 +1,197 @@ +package glum.gui.misc; + +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.plaf.*; + +public class MultiStateCheckBox extends JCheckBox implements MouseListener +{ + // State vars + protected MultiStateModel model; + protected HashMap nextStateMap; + + /** + * Constructor + */ + public MultiStateCheckBox(String text, boolean is3StateCycle) + { + super(text); + + // Set up our replacement icon + super.setIcon(new MultiStateIcon()); + + // Construct the cycle order of the MultiState checkbox + nextStateMap = new HashMap(); + nextStateMap.put(false, MultiState.Checked); + nextStateMap.put(true, MultiState.None); + if (is3StateCycle == false) + { + nextStateMap.put(MultiState.Checked, MultiState.None); + nextStateMap.put(MultiState.None, MultiState.Checked); + nextStateMap.put(MultiState.Mixed, MultiState.None); + } + else + { + nextStateMap.put(MultiState.None, MultiState.Checked); + nextStateMap.put(MultiState.Checked, MultiState.Mixed); + nextStateMap.put(MultiState.Mixed, MultiState.None); +// nextStateMap.put(MultiState.Mixed, MultiState.Crossed); + } + + // Register for mouse events + super.addMouseListener(this); + + // Reset the keyboard action map + rebuildKeyboardMap(); + + // set the model to the adapted model + model = new MultiStateModel(getModel(), nextStateMap); + setModel(model); + setState(MultiState.None); + } + + public MultiStateCheckBox(String text, HashMap aNextStateMap) + { + super(text, false); + + // Insanity check + if (aNextStateMap == null) + throw new NullPointerException(); + + // Save off the custom cycle order of the MultiState checkbox + nextStateMap = aNextStateMap; + } + + public MultiStateCheckBox(String text) + { + this(text, false); + } + + public MultiStateCheckBox() + { + this(null); + } + + @Override + public void doClick() + { + MouseEvent aEvent; + + aEvent = new MouseEvent(this, MouseEvent.MOUSE_PRESSED, 0, 0, 0, 0, 0, false); + handleMouseEvent(aEvent); + model.advanceToNextState(); + } + + /** No one may add mouse listeners, not even Swing! */ + @Override + public void addMouseListener(MouseListener l) + { + } + + /** No one may set a new icon */ + @Override + public void setIcon(Icon icon) + { + } + + // @formatter:off + /** + * MouseListener interface methods + */ + @Override public void mouseClicked(MouseEvent e) { handleMouseEvent(e); } + @Override public void mouseEntered(MouseEvent e) { handleMouseEvent(e); } + @Override public void mouseExited(MouseEvent e) { handleMouseEvent(e); } + @Override public void mousePressed(MouseEvent e) { handleMouseEvent(e); } + @Override public void mouseReleased(MouseEvent e) { handleMouseEvent(e); } + // @formatter:on + + /** + * Return the current state of the Checkbox + */ + public MultiState getState() + { + return model.getState(); + } + + @Override + public void setSelected(boolean selected) + { + if (selected) + setState(MultiState.Checked); + else + setState(MultiState.None); + } + + /** + * setState + */ + public void setState(MultiState state) + { + model.setState(state); + + repaint(); + } + + /** + * Utility method for handling various MouseEvents + */ + protected void handleMouseEvent(MouseEvent e) + { + int aID; + + aID = e.getID(); + if (aID == MouseEvent.MOUSE_ENTERED) + { + model.setArmed(true); + } + else if (aID == MouseEvent.MOUSE_EXITED) + { + model.setArmed(false); + } + else if (aID == MouseEvent.MOUSE_RELEASED) + { + if (model.isArmed() == true) + model.advanceToNextState(); + + model.setPressed(false); + } + else if (aID == MouseEvent.MOUSE_PRESSED) + { + grabFocus(); + model.setPressed(true); + } + } + + /** + * Utility method to set up a custom keyboard map + */ + protected void rebuildKeyboardMap() + { + ActionMap map; + + map = new ActionMapUIResource(); + + map.put("pressed", new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + model.setPressed(true); + } + }); + + map.put("released", new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + model.setPressed(false); + model.advanceToNextState(); + } + }); + + SwingUtilities.replaceUIActionMap(this, map); + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBoxCellEditor.java b/src/glum/gui/misc/MultiStateCheckBoxCellEditor.java new file mode 100644 index 0000000..f0cd124 --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBoxCellEditor.java @@ -0,0 +1,68 @@ +package glum.gui.misc; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.LinkedList; +import javax.swing.AbstractCellEditor; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; + +public class MultiStateCheckBoxCellEditor extends AbstractCellEditor implements ActionListener, TableCellEditor +{ + // State vars + protected Collection myListeners; + protected MultiStateCheckBox refMultiStateCheckBox; + + /** + * Constructor + */ + public MultiStateCheckBoxCellEditor() + { + myListeners = new LinkedList(); + refMultiStateCheckBox = new MultiStateCheckBox("", false); + refMultiStateCheckBox.addActionListener(this); + } + + public void addActionListener(ActionListener aListener) + { + myListeners.add(aListener); + } + + public void removeActionListener(ActionListener aListener) + { + myListeners.remove(aListener); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + fireEditingStopped(); + + aEvent = new ActionEvent(this, aEvent.getID(), "MultiStateCheckBoxCell edited."); + for (ActionListener aListener : myListeners) + aListener.actionPerformed(aEvent); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) + { + // Update our checkbox with the appropriate state + refMultiStateCheckBox.removeActionListener(this); + if (value instanceof MultiState) + refMultiStateCheckBox.setState((MultiState)value); + else if (value instanceof Boolean) + refMultiStateCheckBox.setSelected((Boolean)value); + refMultiStateCheckBox.addActionListener(this); + + return refMultiStateCheckBox; + } + + @Override + public Object getCellEditorValue() + { + return refMultiStateCheckBox.getState(); + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBoxCellRenderer.java b/src/glum/gui/misc/MultiStateCheckBoxCellRenderer.java new file mode 100644 index 0000000..2aa4e4f --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBoxCellRenderer.java @@ -0,0 +1,40 @@ +package glum.gui.misc; + +import java.awt.Component; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; + +public class MultiStateCheckBoxCellRenderer extends MultiStateCheckBox implements TableCellRenderer +{ + // State vars + protected MultiStateCheckBox refMultiStateCheckBox; + + /** + * Constructor + */ + public MultiStateCheckBoxCellRenderer() + { + refMultiStateCheckBox = new MultiStateCheckBox("", false); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + if (value instanceof MultiState) + { + refMultiStateCheckBox.setState((MultiState)value); + return refMultiStateCheckBox; + } + else if (value instanceof Boolean) + { + if ((Boolean)value == true) + refMultiStateCheckBox.setState(MultiState.Checked); + else + refMultiStateCheckBox.setState(MultiState.None); + return refMultiStateCheckBox; + } + + return null; + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBoxHeader.java b/src/glum/gui/misc/MultiStateCheckBoxHeader.java new file mode 100644 index 0000000..4f7ef92 --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBoxHeader.java @@ -0,0 +1,174 @@ +package glum.gui.misc; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.table.*; + +public class MultiStateCheckBoxHeader extends MultiStateCheckBox implements TableCellRenderer, MouseListener, MouseMotionListener +{ + // State vars + protected JTableHeader refHeader; + protected int column; + + /** + * Constructor + */ + public MultiStateCheckBoxHeader(JTable aTable, boolean is3StateCycle) + { + super("", is3StateCycle); + + // Register for mouse events on the table header + refHeader = aTable.getTableHeader(); + if (refHeader != null) + { + refHeader.addMouseListener(this); + refHeader.addMouseMotionListener(this); + } + else + { + System.out.println("Failed to register a mouse listener onto the table header."); + } + } + + /** + * Returns the column associated with the mouse event + */ + public int getAssociatedColumn(MouseEvent aEvent) + { + JTableHeader aHeader; + JTable aTable; + TableColumnModel aColumnModel; + int viewCol, refCol; + + if (aEvent.getSource() instanceof JTableHeader == false) + return -1; + + aHeader = (JTableHeader)aEvent.getSource(); + aTable = aHeader.getTable(); + aColumnModel = aTable.getColumnModel(); + viewCol = aColumnModel.getColumnIndexAtX(aEvent.getX()); + refCol = aTable.convertColumnIndexToModel(viewCol); + + return viewCol; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int aColumn) + { + + if (table != null) + { + JTableHeader header = table.getTableHeader(); + if (header != null) + { + setForeground(header.getForeground()); + setBackground(header.getBackground()); + setFont(header.getFont()); + + // Perhaps we should deregister our listener and register in case we get a new header + ; + } + } + + column = aColumn; + setText((value == null) ? "" : value.toString()); + setBorder(UIManager.getBorder("TableHeader.cellBorder")); + + return this; + } + + @Override + public void mouseClicked(MouseEvent e) + { + // Go back to the deactivated state + model.setPressed(false); + ((Component)e.getSource()).repaint(); + } + + @Override + public void mousePressed(MouseEvent e) + { + // Bail if this mouse event does not correspond to our column + if (getAssociatedColumn(e) != column) + return; + + // Always activate on mouse press + model.setPressed(true); + ((Component)e.getSource()).repaint(); + } + + @Override + public void mouseReleased(MouseEvent e) + { + // Bail if this mouse event does not correspond to our column + if (getAssociatedColumn(e) != column) + return; + + // Advance to the next state (if armed) + if (model.isArmed() == true) + model.advanceToNextState(); + else + model.setArmed(true); + + // Always deactivate on mouse release + model.setPressed(false); + ((Component)e.getSource()).repaint(); + } + + @Override + public void mouseEntered(MouseEvent e) + { + // Bail if this mouse event does not correspond to our column + if (getAssociatedColumn(e) != column) + return; + + // Activate our model + model.setArmed(true); + ((Component)e.getSource()).repaint(); + } + + @Override + public void mouseExited(MouseEvent e) + { + model.setArmed(false); + ((Component)e.getSource()).repaint(); + } + + @Override + public void mouseMoved(MouseEvent e) + { + // Deactivate if not in our column + if (getAssociatedColumn(e) != column) + { + model.setArmed(false); + model.setPressed(false); + } + // Activate if we are in our column + else + { + model.setArmed(true); + } + + ((Component)e.getSource()).repaint(); + } + + @Override + public void mouseDragged(MouseEvent e) + { + // Always deactivate whenever dragging starts to occur + model.setArmed(false); + model.setPressed(false); + ((Component)e.getSource()).repaint(); + } + + @Override + public void setState(MultiState aState) + { + super.setState(aState); + + if (refHeader != null) + refHeader.repaint(); + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBoxHeaderTest.java b/src/glum/gui/misc/MultiStateCheckBoxHeaderTest.java new file mode 100644 index 0000000..d701de4 --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBoxHeaderTest.java @@ -0,0 +1,83 @@ +package glum.gui.misc; + +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.table.*; + +public class MultiStateCheckBoxHeaderTest extends JFrame implements ActionListener +{ + public MultiStateCheckBoxHeaderTest() + { + super("MultiStateCheckBoxHeaderTest"); + + buildGui(); + + } + + /** + * Test application entry point + */ + public static void main(String[] argv) + { + MultiStateCheckBoxHeaderTest mainClass; + + mainClass = new MultiStateCheckBoxHeaderTest(); + + mainClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + mainClass.setVisible(true); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + String text; + + source = e.getSource(); + text = ""; + if (source instanceof AbstractButton) + text = ((AbstractButton)source).getText(); + + System.out.println("HeaderListener::actionPerformed() " + text + ", state:" + ((MultiStateCheckBoxHeader)source).getState()); + } + + /** + * Utility method to build the main GUI + */ + private void buildGui() + { + JTable table; + DefaultTableModel dataModel; + JScrollPane scrollpane; + MultiStateCheckBoxHeader aHeader; + + // Create sample content for the JTable, don't care + String[][] data = new String[7][5]; + String[] headers = new String[5]; + for (int col = 0; col < data[0].length; col++) + { + headers[col] = "- " + col + " -"; + for (int row = 0; row < data.length; row++) + data[row][col] = "(" + row + "," + col + ")"; + } + dataModel = new DefaultTableModel(data, headers); + table = new JTable(dataModel); + + // Create a HeaderCellRenderer for each column + Enumeration enumeration = table.getColumnModel().getColumns(); + while (enumeration.hasMoreElements()) + { + TableColumn aColumn = enumeration.nextElement(); + aHeader = new MultiStateCheckBoxHeader(table, true); + aHeader.addActionListener(this); + aColumn.setHeaderRenderer(aHeader); + } + + scrollpane = new JScrollPane(table); + getContentPane().add(scrollpane); + + pack(); + } + +} diff --git a/src/glum/gui/misc/MultiStateCheckBoxTest.java b/src/glum/gui/misc/MultiStateCheckBoxTest.java new file mode 100644 index 0000000..089acb2 --- /dev/null +++ b/src/glum/gui/misc/MultiStateCheckBoxTest.java @@ -0,0 +1,34 @@ +package glum.gui.misc; + +import java.awt.*; +import javax.swing.*; + +public class MultiStateCheckBoxTest +{ + public static void main(String args[]) throws Exception + { + JFrame frame; + JCheckBox multi1CB, multi2CB; + JCheckBox standardCB; + + frame = new JFrame("MultiStateCheckBoxTest"); + frame.getContentPane().setLayout(new GridLayout(0, 1, 5, 5)); + + standardCB = new JCheckBox("Standard checkbox"); + standardCB.setMnemonic('S'); + frame.getContentPane().add(standardCB); + + multi1CB = new MultiStateCheckBox("Multistate-1 checkbox", false); + multi1CB.setMnemonic('1'); + frame.getContentPane().add(multi1CB); + + multi2CB = new MultiStateCheckBox("Multistate-2 checkbox", true); + multi2CB.setMnemonic('2'); + frame.getContentPane().add(multi2CB); + + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + +} diff --git a/src/glum/gui/misc/MultiStateIcon.java b/src/glum/gui/misc/MultiStateIcon.java new file mode 100644 index 0000000..14a5b2d --- /dev/null +++ b/src/glum/gui/misc/MultiStateIcon.java @@ -0,0 +1,114 @@ +package glum.gui.misc; + +import java.awt.*; +import javax.swing.*; +import javax.swing.plaf.metal.*; + +public class MultiStateIcon implements Icon +{ + @Override + public int getIconWidth() + { + return getIconSize(); + } + + @Override + public int getIconHeight() + { + return getIconSize(); + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) + { + MultiStateModel model; + MultiState aState; + int iconSize; + + // Insanity check + if (c instanceof MultiStateCheckBox == false) + return; + + model = (MultiStateModel)((JCheckBox)c).getModel(); + iconSize = getIconSize(); + + if (model.isEnabled()) + { + if (model.isPressed() && model.isArmed()) + { + g.setColor(MetalLookAndFeel.getControlShadow()); + g.fillRect(x, y, iconSize - 1, iconSize - 1); + drawPressed3DBorder(g, x, y, iconSize, iconSize); + } + else + { + if (model.isArmed() == true) + { + g.setColor(MetalLookAndFeel.getControlShadow()); + drawPressed3DBorder(g, x, y, iconSize, iconSize); + } + else + { + drawFlush3DBorder(g, x, y, iconSize, iconSize); + } + } + + g.setColor(MetalLookAndFeel.getControlInfo()); + } + else + { + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawRect(x, y, iconSize - 1, iconSize - 1); + } + + // Render the appropriate symbol + aState = model.getState(); + aState.render(g, x, y, iconSize); + } + + /** + * Utility method + */ + protected void drawFlush3DBorder(Graphics g, int x, int y, int w, int h) + { + g.translate(x, y); + g.setColor(MetalLookAndFeel.getControlDarkShadow()); + g.drawRect(0, 0, w - 2, h - 2); + g.setColor(MetalLookAndFeel.getControlHighlight()); + g.drawRect(1, 1, w - 2, h - 2); + g.setColor(MetalLookAndFeel.getControl()); + g.drawLine(0, h - 1, 1, h - 2); + g.drawLine(w - 1, 0, w - 2, 1); + g.translate(-x, -y); + } + + /** + * Utility method + */ + protected void drawPressed3DBorder(Graphics g, int x, int y, int w, int h) + { + g.translate(x, y); + drawFlush3DBorder(g, 0, 0, w, h); + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(1, 1, 1, h - 2); + g.drawLine(1, 1, w - 2, 1); + g.drawLine(2, 1, 2, h - 2); + g.drawLine(1, 2, w - 2, 2); + + g.drawLine(w - 2, 1, w - 2, h - 2); + g.drawLine(1, h - 2, w - 2, h - 2); + g.drawLine(w - 3, 1, w - 3, h - 2); + g.drawLine(1, h - 3, w - 2, h - 3); + + g.translate(-x, -y); + } + + /** + * Returns the square dimensions of this GUI + */ + protected int getIconSize() + { + return 13; + } + +} diff --git a/src/glum/gui/misc/MultiStateModel.java b/src/glum/gui/misc/MultiStateModel.java new file mode 100644 index 0000000..888a91c --- /dev/null +++ b/src/glum/gui/misc/MultiStateModel.java @@ -0,0 +1,181 @@ +package glum.gui.misc; + +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; + +public class MultiStateModel implements ButtonModel +{ + protected final ButtonModel refModel; + protected final HashMap nextStateMap; + protected MultiState myState; + + public MultiStateModel(ButtonModel aRefModel, HashMap aNextStateMap) + { + refModel = aRefModel; + nextStateMap = aNextStateMap; + + myState = MultiState.None; + } + + public void advanceToNextState() + { + MultiState aState; + + aState = nextStateMap.get(myState); + if (aState == null) + aState = MultiState.None; + + setState(aState); + } + + public MultiState getState() + { + return myState; + } + + public void setState(MultiState state) + { + this.myState = state; + } + + @Override + public boolean isSelected() + { + if (myState != MultiState.None) + return true; + + return false; + } + + /** + * All these methods simply delegate to the "refModel" model that is being decorated. + */ + @Override + public boolean isArmed() + { + return refModel.isArmed(); + } + + @Override + public boolean isEnabled() + { + return refModel.isEnabled(); + } + + @Override + public boolean isPressed() + { + return refModel.isPressed(); + } + + @Override + public boolean isRollover() + { + return refModel.isRollover(); + } + + @Override + public void setArmed(boolean b) + { + refModel.setArmed(b); + } + + @Override + public void setEnabled(boolean b) + { + refModel.setEnabled(b); + } + + @Override + public void setSelected(boolean b) + { + refModel.setSelected(b); + } + + @Override + public void setPressed(boolean b) + { + refModel.setPressed(b); + } + + @Override + public void setRollover(boolean b) + { + refModel.setRollover(b); + } + + @Override + public void setMnemonic(int key) + { + refModel.setMnemonic(key); + } + + @Override + public int getMnemonic() + { + return refModel.getMnemonic(); + } + + @Override + public void setActionCommand(String s) + { + refModel.setActionCommand(s); + } + + @Override + public String getActionCommand() + { + return refModel.getActionCommand(); + } + + @Override + public void setGroup(ButtonGroup group) + { + refModel.setGroup(group); + } + + @Override + public void addActionListener(ActionListener l) + { + refModel.addActionListener(l); + } + + @Override + public void removeActionListener(ActionListener l) + { + refModel.removeActionListener(l); + } + + @Override + public void addItemListener(ItemListener l) + { + refModel.addItemListener(l); + } + + @Override + public void removeItemListener(ItemListener l) + { + refModel.removeItemListener(l); + } + + @Override + public void addChangeListener(ChangeListener l) + { + refModel.addChangeListener(l); + } + + @Override + public void removeChangeListener(ChangeListener l) + { + refModel.removeChangeListener(l); + } + + @Override + public Object[] getSelectedObjects() + { + return refModel.getSelectedObjects(); + } + +} diff --git a/src/glum/gui/misc/SimpleTitledBorder.java b/src/glum/gui/misc/SimpleTitledBorder.java new file mode 100644 index 0000000..9e4c3da --- /dev/null +++ b/src/glum/gui/misc/SimpleTitledBorder.java @@ -0,0 +1,60 @@ +package glum.gui.misc; + +import java.awt.*; +import javax.swing.*; +import javax.swing.text.*; +import javax.swing.border.*; + +public class SimpleTitledBorder extends TitledBorder +{ + // Class var used to strip the disabled color + private static JTextComponent testComponent = null; + + /** + * Constuctor + */ + public SimpleTitledBorder(Border border) + { + super(border); + } + + public SimpleTitledBorder(Border border, String title) + { + super(border, title); + } + + public SimpleTitledBorder(Border border, String title, int titleJustification, int titlePosition) + { + super(border, title, titleJustification, titlePosition); + } + + public SimpleTitledBorder(Border border, String title, int titleJustification, int titlePosition, Font titleFont) + { + super(border, title, titleJustification, titlePosition, titleFont); + } + + public SimpleTitledBorder(Border border, String title, int titleJustification, int titlePosition, Font titleFont, Color titleColor) + { + super(border, title, titleJustification, titlePosition, titleFont, titleColor); + } + + public SimpleTitledBorder(String title) + { + super(title); + } + + /** + * setEnabled + */ + public void setEnabled(boolean aBool) + { + if (testComponent == null) + testComponent = new JTextField(); + + // ! TODO: Color should be based on system settings + setTitleColor(Color.BLACK); + if (aBool == false) + setTitleColor(testComponent.getDisabledTextColor()); + } + +} diff --git a/src/glum/gui/panel/CardPanel.java b/src/glum/gui/panel/CardPanel.java new file mode 100644 index 0000000..c8329b0 --- /dev/null +++ b/src/glum/gui/panel/CardPanel.java @@ -0,0 +1,89 @@ +package glum.gui.panel; + +import java.awt.*; +import java.util.*; +import javax.swing.*; + +import com.google.common.collect.*; + +public class CardPanel extends JPanel +{ + protected BiMap myMap; + protected CardLayout myLayout; + protected G1 activeCard; + + public CardPanel() + { + super(); + + myLayout = new CardLayout(); + setLayout(myLayout); + + myMap = HashBiMap.create(); + activeCard = null; + } + + @Override + public Component add(String name, Component comp) + { + throw new RuntimeException("Improper method call. Use addCard() instead of add()"); + } + + public void addCard(String aTitle, G1 aComponent) + { + // aComponent must be of type Component + if ((aComponent instanceof Component) == false) + throw new IllegalArgumentException("aComponent must be of type Component. Found class: " + aComponent.getClass().getName()); + + // Add the component if no component associated with the key + if (myMap.get(aTitle) == null) + { + myMap.put(aTitle, aComponent); + add((Component)aComponent, aTitle); + } + // If the key is associated, then ensure it is matched to aComponent + else if (myMap.get(aTitle) != aComponent) + { + throw new RuntimeException("Attempting to add new card with an already inserted key: " + aTitle); + } + + switchToCard(aTitle); + } + + public G1 getActiveCard() + { + return activeCard; + } + + public Collection getAllCards() + { + Collection itemList; + + itemList = new ArrayList(myMap.values()); + return itemList; + } + + public Set getCardNames() + { + return new HashSet(myMap.keySet()); + } + + public void switchToCard(String aTitle) + { + activeCard = myMap.get(aTitle); + if (activeCard == null) + throw new RuntimeException("No mapping found when switching to card: " + aTitle); + + myLayout.show(this, aTitle); + } + + public void switchToCard(G1 aCard) + { + if (myMap.values().contains(aCard) == false) + throw new RuntimeException("No mapping found when switching to card: " + aCard); + + activeCard = aCard; + myLayout.show(this, myMap.inverse().get(aCard)); + } + +} diff --git a/src/glum/gui/panel/ColorInputPanel.java b/src/glum/gui/panel/ColorInputPanel.java new file mode 100644 index 0000000..55767b5 --- /dev/null +++ b/src/glum/gui/panel/ColorInputPanel.java @@ -0,0 +1,367 @@ +package glum.gui.panel; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; + +import net.miginfocom.swing.MigLayout; + +import glum.gui.component.GNumberField; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.UnitProvider; + +public class ColorInputPanel extends JPanel implements ActionListener, ChangeListener +{ + // Constants + private static final Font miniFont = new Font("Serif", Font.PLAIN, 10); + + // Gui components + private ColorPanel colorP; + private JLabel redL, greenL, blueL; + private JSlider redS, greenS, blueS; + private GNumberField redNF, greenNF, blueNF; + + // State vars + private Collection myActionListeners; + + /** + * Constructor + */ + public ColorInputPanel(boolean isHorizontal, boolean showTF) + { + super(); + + // Init internal vars + myActionListeners = new LinkedHashSet(); + + // Build the gui areas + buildGuiArea(isHorizontal, showTF); + + // Set in the default color + setColor(Color.BLACK); + } + + /** + * addActionListener + */ + public synchronized void addActionListener(ActionListener aActionListener) + { + // Insanity check + if (aActionListener == null) + return; + + myActionListeners.add(aActionListener); + } + + /** + * removeActionListener + */ + public synchronized void removeActionListener(ActionListener aActionListener) + { + // Insanity check + if (aActionListener == null) + return; + + myActionListeners.remove(aActionListener); + } + + /** + * Returns the selected color + */ + public Color getColor() + { + int redVal, greenVal, blueVal; + + redVal = redS.getValue(); + greenVal = greenS.getValue(); + blueVal = blueS.getValue(); + return new Color(redVal, greenVal, blueVal); + } + + /** + * Sets in the current selected color + */ + public void setColor(Color aColor) + { + // Insanity check + if (aColor == null) + return; + + synchronizeGui(aColor); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + // Perform GUI updates + source = aEvent.getSource(); + updateGui(source); + + // Notify the listeners + fireActionEvent(false); + } + + @Override + public void setEnabled(boolean aBool) + { + redL.setEnabled(aBool); + redS.setEnabled(aBool); + redNF.setEnabled(aBool); + greenL.setEnabled(aBool); + greenS.setEnabled(aBool); + greenNF.setEnabled(aBool); + blueL.setEnabled(aBool); + blueS.setEnabled(aBool); + blueNF.setEnabled(aBool); + + colorP.setEnabled(aBool); + } + + @Override + public void stateChanged(ChangeEvent aEvent) + { + Object source; + JSlider aSlider; + + // Perform GUI updates + source = aEvent.getSource(); + updateGui(source); + + // Notify the listeners + if (source instanceof JSlider) + { + // Fire off an event only if not being updated + aSlider = (JSlider)source; + if (aSlider.getValueIsAdjusting() == false) + fireActionEvent(false); + else + fireActionEvent(true); + } + } + + /** + * Forms the actual gui + */ + private void buildGuiArea(boolean isHorizontal, boolean showTF) + { + JPanel rPanel, gPanel, bPanel; + UnitProvider countUP; + int sliderStyle; + + sliderStyle = JSlider.HORIZONTAL; + if (isHorizontal == false) + sliderStyle = JSlider.VERTICAL; + + countUP = new ConstUnitProvider(new NumberUnit("", "", 1.0, 0)); + + // RGB sliders + redL = new JLabel("R", JLabel.CENTER); + redS = new JSlider(sliderStyle, 0, 255, 0); + redNF = new GNumberField(this, countUP, 0, 255); + rPanel = formColorControl(redS, redL, redNF, isHorizontal, showTF); + + greenL = new JLabel("G", JLabel.CENTER); + greenS = new JSlider(sliderStyle, 0, 255, 0); + greenNF = new GNumberField(this, countUP, 0, 255); + gPanel = formColorControl(greenS, greenL, greenNF, isHorizontal, showTF); + + blueL = new JLabel("B", JLabel.CENTER); + blueS = new JSlider(sliderStyle, 0, 255, 0); + blueNF = new GNumberField(this, countUP, 0, 255); + bPanel = formColorControl(blueS, blueL, blueNF, isHorizontal, showTF); + + // The color area + colorP = new ColorPanel(40, 40); + + if (isHorizontal == true) + { + setLayout(new MigLayout("", "0[grow,75::][]0", "0[][][]0")); + + add(rPanel, "growx,span 1,wrap"); + add(gPanel, "growx,span 1,wrap"); + add(bPanel, "growx,span 1,wrap"); + add(colorP, "cell 1 0,growy,spanx 1,spany 3"); + } + else + { + setLayout(new MigLayout("", "0[][][]0", "0[grow,75::][]0")); + + add(rPanel, "growy,span 1"); + add(gPanel, "growy,span 1"); + add(bPanel, "growy,span 1,wrap"); + add(colorP, "growx,span 3"); + } + } + + /** + * builds a JSlider with a label + */ + private JPanel formColorControl(JSlider aS, JLabel aL, GNumberField aNF, boolean isHorizontal, boolean showTF) + { + JPanel aPanel; + + aPanel = new JPanel(); + if (isHorizontal == true) + { + aPanel.setLayout(new BoxLayout(aPanel, BoxLayout.X_AXIS)); + aL.setAlignmentY(0.5f); + aS.setAlignmentY(0.5f); + aNF.setAlignmentY(0.5f); + } + else + { + aPanel.setLayout(new BoxLayout(aPanel, BoxLayout.Y_AXIS)); + aL.setAlignmentX(0.5f); + aS.setAlignmentX(0.5f); + aNF.setAlignmentX(0.5f); + } + + aS.addChangeListener(this); + + aNF.setHorizontalAlignment(JTextField.CENTER); + aNF.setColumns(3); + aNF.setValue(0); + aNF.setFont(miniFont); + aNF.setMinimumSize(aNF.getPreferredSize()); + aNF.setMaximumSize(aNF.getPreferredSize()); + + aPanel.add(aL); + aPanel.add(aS); + + if (isHorizontal == true) + aPanel.add(Box.createHorizontalStrut(2)); + else + aPanel.add(Box.createVerticalStrut(2)); + + if (showTF == true) + aPanel.add(aNF); + + return aPanel; + } + + /** + * Notifies all listeners of color change + */ + private void fireActionEvent(boolean isChanging) + { + Collection currListeners; + ActionEvent aEvent; + + // Get a copy of the current set of listeners + synchronized(this) + { + currListeners = new LinkedHashSet(myActionListeners); + } + + // Construct the event + if (isChanging == false) + aEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Color changed."); + else + aEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Color changing."); + + // Notify our listeners + for (ActionListener aListener : currListeners) + { + aListener.actionPerformed(aEvent); + } + } + + /** + * Syncs the GUI to match aColor + */ + private void synchronizeGui(Color aColor) + { + int redVal, greenVal, blueVal; + + // Get the rgb values + redVal = aColor.getRed(); + greenVal = aColor.getGreen(); + blueVal = aColor.getBlue(); + + // Stop listening to events while updating + redS.removeChangeListener(this); + greenS.removeChangeListener(this); + blueS.removeChangeListener(this); + + // Update the gui components + if (redVal != redNF.getValue()) + redNF.setValue(redVal); + if (greenVal != greenNF.getValue()) + greenNF.setValue(greenVal); + if (blueVal != blueNF.getValue()) + blueNF.setValue(blueVal); + redS.setValue(redVal); + greenS.setValue(greenVal); + blueS.setValue(blueVal); + colorP.setColor(new Color(redVal, greenVal, blueVal)); + + // Proceed with listening to events + redS.addChangeListener(this); + greenS.addChangeListener(this); + blueS.addChangeListener(this); + } + + /** + * Updates the gui to reflect the source that has changed + */ + private void updateGui(Object source) + { + int redVal, greenVal, blueVal; + + // Determine what values to retrieve based on the source + if (source instanceof GNumberField) + { + redVal = redNF.getValueAsInt(0); + greenVal = greenNF.getValueAsInt(0); + blueVal = blueNF.getValueAsInt(0); + } + else + { + // Get the slider values + redVal = redS.getValue(); + greenVal = greenS.getValue(); + blueVal = blueS.getValue(); + } + + // Update the appropriate component + if (source == redS) + { + redNF.setValue(redVal); + } + else if (source == greenS) + { + greenNF.setValue(greenVal); + } + else if (source == blueS) + { + blueNF.setValue(blueVal); + } + else if (source == redNF) + { + redS.removeChangeListener(this); + redS.setValue(redVal); + redS.addChangeListener(this); + } + else if (source == greenNF) + { + greenS.removeChangeListener(this); + greenS.setValue(greenVal); + greenS.addChangeListener(this); + } + else if (source == blueNF) + { + blueS.removeChangeListener(this); + blueS.setValue(blueVal); + blueS.addChangeListener(this); + } + + // Update the preview color + colorP.setColor(new Color(redVal, greenVal, blueVal)); + } + +} diff --git a/src/glum/gui/panel/ColorPanel.java b/src/glum/gui/panel/ColorPanel.java new file mode 100644 index 0000000..aa5eef1 --- /dev/null +++ b/src/glum/gui/panel/ColorPanel.java @@ -0,0 +1,66 @@ +package glum.gui.panel; + +import java.awt.Color; +import java.awt.Dimension; + +import javax.swing.JPanel; +import javax.swing.border.BevelBorder; +import javax.swing.border.LineBorder; + +public class ColorPanel extends JPanel +{ + // State vars + protected Color dispColor; + + public ColorPanel() + { + super(); + + dispColor = Color.BLACK; + updateGui(); + } + + public ColorPanel(int sizeX, int sizeY) + { + this(); + + setMinimumSize(new Dimension(sizeX, sizeY)); + setPreferredSize(new Dimension(sizeX, sizeY)); + } + + /** + * Sets in the color that is displayed by this component + */ + public void setColor(Color aColor) + { + dispColor = aColor; + updateGui(); + } + + @Override + public void setEnabled(boolean aBool) + { + super.setEnabled(aBool); + updateGui(); + } + + /** + * Updates the GUI to reflect the chosen color + */ + protected void updateGui() + { + boolean isEnabled; + + isEnabled = isEnabled(); + if (isEnabled == false) + { + setBackground(Color.LIGHT_GRAY); + setBorder(new LineBorder(Color.GRAY)); + return; + } + + setBackground(dispColor); + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + +} diff --git a/src/glum/gui/panel/ComponentTracker.java b/src/glum/gui/panel/ComponentTracker.java new file mode 100644 index 0000000..7fb0dd8 --- /dev/null +++ b/src/glum/gui/panel/ComponentTracker.java @@ -0,0 +1,156 @@ +package glum.gui.panel; + +import java.awt.Component; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Utility class to allow a component to track other Component so it can keep it's + * properties synchronized with the tracked Components. + */ +public class ComponentTracker implements ComponentListener +{ + protected Component targComp; + protected Component trkHiddenComp; + protected Component trkMovedComp; + protected Component trkResizedComp; + protected Component trkShownComp; + + public ComponentTracker(Component aTargComp) + { + targComp = aTargComp; + trkHiddenComp = null; + trkMovedComp = null; + trkResizedComp = null; + trkShownComp = null; + } + + /** + * Track aComp so that if it is hidden, then the reference + * targetComponent will be hidden. + */ + public void setHiddenTracker(Component aComp) + { + // Deregister from the old trkShownComp + if (trkHiddenComp != null) + trkHiddenComp.removeComponentListener(this); + + trkHiddenComp = aComp; + updateListenersForTrackedComponents(); + } + + /** + * Track aComp so that if it is moved, then the reference + * targetComponent will be moved. + */ + public void setMovedTracker(Component aComp) + { + // Deregister from the old trkShownComp + if (trkMovedComp != null) + trkMovedComp.removeComponentListener(this); + + trkMovedComp = aComp; + updateListenersForTrackedComponents(); + } + + /** + * Track aComp so that if it is resized, then the reference + * targetComponent will be resized. + */ + public void setResizedTracker(Component aComp) + { + // Deregister from the old trkShownComp + if (trkResizedComp != null) + trkResizedComp.removeComponentListener(this); + + trkResizedComp = aComp; + updateListenersForTrackedComponents(); + } + + /** + * Track aComp so that if it is shown, then the reference + * targetComponent will be shown. + */ + public void setShownTracker(Component aComp) + { + // Deregister from the old trkShownComp + if (trkShownComp != null) + trkShownComp.removeComponentListener(this); + + trkShownComp = aComp; + updateListenersForTrackedComponents(); + } + + @Override + public void componentHidden(ComponentEvent aEvent) + { + if (aEvent.getComponent() == trkHiddenComp) + targComp.setVisible(false); + } + + @Override + public void componentMoved(ComponentEvent aEvent) + { + if (aEvent.getComponent() == trkMovedComp) + targComp.setLocation(trkMovedComp.getLocation()); + } + + @Override + public void componentResized(ComponentEvent aEvent) + { + if (aEvent.getComponent() == trkResizedComp) + { + targComp.setSize(trkResizedComp.getSize()); + targComp.validate(); + } + } + + @Override + public void componentShown(ComponentEvent aEvent) + { + if (aEvent.getComponent() == trkShownComp) + targComp.setVisible(true); + } + + /** + * Utility method to ensure that we are registered (Component events) for + * all component which are being tracked. Note that at most we will register + * only only once for each unique Component. + */ + protected void updateListenersForTrackedComponents() + { + List listenerList; + + if (trkHiddenComp != null) + { + listenerList = Lists.newArrayList(trkHiddenComp.getComponentListeners()); + if (listenerList.contains(this) == false) + trkHiddenComp.addComponentListener(this); + } + + if (trkMovedComp != null) + { + listenerList = Lists.newArrayList(trkMovedComp.getComponentListeners()); + if (listenerList.contains(this) == false) + trkMovedComp.addComponentListener(this); + } + + if (trkResizedComp != null) + { + listenerList = Lists.newArrayList(trkResizedComp.getComponentListeners()); + if (listenerList.contains(this) == false) + trkResizedComp.addComponentListener(this); + } + + if (trkShownComp != null) + { + listenerList = Lists.newArrayList(trkShownComp.getComponentListeners()); + if (listenerList.contains(this) == false) + trkShownComp.addComponentListener(this); + } + } + +} diff --git a/src/glum/gui/panel/CredentialPanel.java b/src/glum/gui/panel/CredentialPanel.java new file mode 100644 index 0000000..5b9dd89 --- /dev/null +++ b/src/glum/gui/panel/CredentialPanel.java @@ -0,0 +1,289 @@ +package glum.gui.panel; + +import glum.net.Credential; +import glum.net.NetUtil; +import glum.net.Result; +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GPasswordField; +import glum.gui.component.GTextField; +import glum.gui.panel.GlassPanel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; + +import net.miginfocom.swing.MigLayout; + +public class CredentialPanel extends GlassPanel implements ActionListener +{ + // Constants + private static final Color warnColor = new Color(128, 0, 0); + + // GUI vars + protected JLabel titleL; + protected JButton ignoreB, acceptB; + protected JPasswordField passTF; + protected GTextField userTF; + protected JTextField sourceTF, warnTA; + + // State vars + protected Credential myCredential; + protected Result eResult; + protected Boolean isReset; + + /** + * Constructor + */ + public CredentialPanel(Component aParent) + { + super(aParent); + + // State vars + myCredential = null; + eResult = null; + isReset = true; + + // Build the actual GUI + buildGuiArea(); + updateGui(); + + // Set up some keyboard shortcuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(ignoreB)); + } + + public Credential getCredential() + { + return myCredential; + } + + public void setCredential(Credential aCredential, String aSourceUri) + { + // Construct the default (empty) credential + if (aCredential == null) + aCredential = new Credential("", null); + + userTF.removeActionListener(this); + userTF.setText(aCredential.getUsername()); + userTF.addActionListener(this); + + passTF.removeActionListener(this); + passTF.setText(aCredential.getPasswordAsString()); + passTF.addActionListener(this); + + sourceTF.setText(aSourceUri); + sourceTF.setCaretPosition(0); + + // Reset the dialog + isReset = true; + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + updateGui(); + } + }); + } + + public void setTitle(String aTitle) + { + titleL.setText(aTitle); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + + source = e.getSource(); + + if (source == ignoreB) + { + // Hide the dialog + myCredential = null; + setVisible(false); + + // Notify our listeners + notifyListeners(this, 0, "Ignore"); + } + else if (source == acceptB || source == passTF) + { + // Set the GUI into the waiting mode + isReset = null; + updateGui(); + + // Validate the settings + final Credential aCredential = new Credential(userTF.getText(), passTF.getPassword()); + final String uriRoot = sourceTF.getText(); + + new Thread() + { + @Override + public void run() + { + final Result aResult; + + // Pause for 0.5 sec to let the user register the change + try + { + Thread.sleep(500); + } + catch (InterruptedException aExp) + { + aExp.printStackTrace(); + } + + // Test the credentials + aResult = NetUtil.checkCredentials(uriRoot, aCredential); + + // Update the Gui + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + // Bail if the user decided to quit before our results came + // back. + if (isVisible() == false) + return; + + // If the credentials are valid then save them and bail + if (aResult == Result.Success) + { + myCredential = aCredential; + setVisible(false); + + // Notify our listeners + notifyListeners(CredentialPanel.this, 0, "Accept"); + } + // Try again + else + { + eResult = aResult; + isReset = false; + updateGui(); + } + } + }); + } + }.start(); + } + else + { + updateGui(); + } + } + + /** + * Construct the GUI + */ + private void buildGuiArea() + { + Dimension aDimension; + JLabel tmpL; + String aStr; + + // Form the grid bag constraints + setLayout(new MigLayout("", "[right][grow][][]")); + + // Title Area + titleL = new JLabel("Product Name", JLabel.CENTER); + add(titleL, "growx,span 4,wrap"); + + // Source area + tmpL = new JLabel("Source:"); + add(tmpL); + + sourceTF = GuiUtil.createUneditableTextField("http://www.google.edu"); + add(sourceTF, "growx,span 3,wrap"); + + // Username area + tmpL = new JLabel("Username:"); + userTF = new GTextField(this); + add(tmpL); + add(userTF, "growx,span 3,wrap"); + + // Password area + tmpL = new JLabel("Password:"); + passTF = new GPasswordField(this); + add(tmpL); + add(passTF, "growx,span 3,wrap"); + + // Warn area + aStr = "Please enter the credentials for accessing the data."; + warnTA = GuiUtil.createUneditableTextField(aStr); + warnTA.setForeground(warnColor); + add(warnTA, "growx,span 4,wrap"); + + // Action area + aDimension = GuiUtil.computePreferredJButtonSize("Ignore", "Accept"); + ignoreB = GuiUtil.createJButton("Ignore", this, aDimension); + acceptB = GuiUtil.createJButton("Accept", this, aDimension); + add(ignoreB, "skip 2,span 1"); + add(acceptB, "span 1"); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + /** + * Utility method to update the GUI + */ + private void updateGui() + { + boolean isEnabled; + + if (isReset == null) + { + warnTA.setText("Checking the credentials..."); + isEnabled = false; + } + else + { + if (userTF.getText().isEmpty() == true && passTF.getPassword().length < 2) + isReset = true; + + isEnabled = true; + if (isReset == true) + { + warnTA.setText("Please enter the credentials for accessing the data."); + } + else + { + switch (eResult) + { + case BadCredentials: + warnTA.setText("Credentials are invalid."); + break; + + case ConnectFailure: + warnTA.setText("Failed to connect to resource."); + break; + + case UnreachableHost: + warnTA.setText("Unreachable host."); + break; + + default: + warnTA.setText("Unreconzied error. Error: " + eResult); + break; + } + } + } + + // Update the UI elements + userTF.setEnabled(isEnabled); + passTF.setEnabled(isEnabled); + acceptB.setEnabled(isEnabled); + } + +} diff --git a/src/glum/gui/panel/CustomFocusTraversalPolicy.java b/src/glum/gui/panel/CustomFocusTraversalPolicy.java new file mode 100644 index 0000000..a0b8d9f --- /dev/null +++ b/src/glum/gui/panel/CustomFocusTraversalPolicy.java @@ -0,0 +1,155 @@ +package glum.gui.panel; + +import java.awt.Component; +import java.awt.Container; +import java.awt.FocusTraversalPolicy; +import java.util.ArrayList; + +import com.google.common.collect.Lists; + +public class CustomFocusTraversalPolicy extends FocusTraversalPolicy +{ + protected ArrayList itemList; + + public CustomFocusTraversalPolicy() + { + itemList = Lists.newArrayList(); + } + + /** + * Method to add an item to the end of the FocusTraversalPolicy + */ + public void addComponent(Component aItem) + { + itemList.add(aItem); + } + + @Override + public Component getComponentAfter(Container focusCycleRoot, Component aComponent) + { + Component aComp; + int cIndex, tIndex; + + // Bail if the component is not in our list + cIndex = itemList.indexOf(aComponent); + if (cIndex < 0) + return getFirstComponent(focusCycleRoot); + + tIndex = cIndex; + while (true) + { + // Iterate through the circular loop + tIndex++; + if (tIndex == itemList.size()) + tIndex = 0; + + // Ensure the item is focusable + aComp = itemList.get(tIndex); + if (checkFocusability(aComp) == true) + return aComp; + + // Bail if we have made an full loop + if (tIndex == cIndex) + break; + } + + return itemList.get(cIndex); + } + + @Override + public Component getComponentBefore(Container focusCycleRoot, Component aComponent) + { + Component aComp; + int cIndex, tIndex; + + // Bail if the component is not in our list + cIndex = itemList.indexOf(aComponent); + if (cIndex < 0) + return getLastComponent(focusCycleRoot); + + tIndex = cIndex; + while (true) + { + // Iterate through the circular loop + tIndex--; + if (tIndex == -1) + tIndex = itemList.size() - 1; + + // Ensure the item is focusable + aComp = itemList.get(tIndex); + if (checkFocusability(aComp) == true) + return aComp; + + // Bail if we have made an full loop + if (tIndex == cIndex) + break; + } + + return itemList.get(cIndex); + } + + @Override + public Component getDefaultComponent(Container focusCycleRoot) + { + return getFirstComponent(focusCycleRoot); + } + + @Override + public Component getFirstComponent(Container focusCycleRoot) + { + Component aComp; + + // Bail if no components to traverse + if (itemList.isEmpty() == true) + return null; + + aComp = itemList.get(0); + if (checkFocusability(aComp) == true) + return aComp; + + return getComponentAfter(focusCycleRoot, aComp); + } + + @Override + public Component getLastComponent(Container focusCycleRoot) + { + Component aComp; + + // Bail if no components to traverse + if (itemList.isEmpty() == true) + return null; + + aComp = itemList.get(itemList.size()-1); + if (checkFocusability(aComp) == true) + return aComp; + + return getComponentBefore(focusCycleRoot, aComp); + } + + + /** + * Utility method to test to see if a Component can grab the focus. + * To grab the focus a component must be: + * - focusable + * - enabled + * - visible + * - be in a container that is being shown. + */ + protected boolean checkFocusability(Component aComp) + { + if (aComp.isFocusable() == false) + return false; + + if (aComp.isEnabled() == false) + return false; + + if (aComp.isVisible() == false) + return false; + + if (aComp.isShowing() == false) + return false; + + return true; + } + +} diff --git a/src/glum/gui/panel/GPanel.java b/src/glum/gui/panel/GPanel.java new file mode 100644 index 0000000..bd3804e --- /dev/null +++ b/src/glum/gui/panel/GPanel.java @@ -0,0 +1,50 @@ +package glum.gui.panel; + +import glum.gui.panel.generic.GenericCodes; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +import javax.swing.JPanel; + +import com.google.common.collect.Lists; + +public class GPanel extends JPanel implements GenericCodes +{ + // State vars + protected List myListeners; + + public GPanel() + { + super(); + + myListeners = Lists.newLinkedList(); + } + + /** + * Add an ActionListener to this GPanel + */ + public void addActionListener(ActionListener aListener) + { + myListeners.add(aListener); + } + + /** + * Remove an ActionListener to this GPanel + */ + public void removeActionListener(ActionListener aListener) + { + myListeners.remove(aListener); + } + + /** + * Send out notification to all of the ActionListeners + */ + public void notifyListeners(Object aSource, int aId, String aCommand) + { + for (ActionListener aListener : myListeners) + aListener.actionPerformed(new ActionEvent(aSource, aId, aCommand)); + } + +} diff --git a/src/glum/gui/panel/GlassPane.java b/src/glum/gui/panel/GlassPane.java new file mode 100644 index 0000000..f188489 --- /dev/null +++ b/src/glum/gui/panel/GlassPane.java @@ -0,0 +1,346 @@ +package glum.gui.panel; + +import glum.gui.GuiUtil; +import glum.gui.panel.CustomFocusTraversalPolicy; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.awt.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.Set; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; + +import net.miginfocom.swing.MigLayout; + +import com.google.common.collect.Sets; + +public class GlassPane extends JComponent implements ZioRaw, ComponentListener +{ + // Communicator vars + protected Component parentComp; + protected WaftPanel childComp; + + protected RootPaneContainer rootPane; + + // State vars + protected Color fillColor; + protected CustomFocusTraversalPolicy myFocusPolicy; + protected MigLayout refLayout; + + public GlassPane(Component aRefParent, JComponent aDisplayPane) + { + // Communicator vars + parentComp = aRefParent; + childComp = new WaftPanel(aDisplayPane, this); + + // Build the GUI + refLayout = new MigLayout(); + setLayout(refLayout); + +// add("pos 150px 100px", childComp); + add("", childComp); + this.validate(); + this.revalidate(); + + // Register for events of interest + childComp.addComponentListener(this); + + // Set up the GlassPanelListener handler + GlassPaneListener aListener = new GlassPaneListener(this, childComp); + addMouseListener(aListener); + addMouseMotionListener(aListener); + + rootPane = null; + fillColor = new Color(96, 96, 96, 96); + } + + /** + * Set in an alternative shade color. + */ + public void setFillColor(Color aColor) + { + fillColor = aColor; + } + + /** + * Method to change the end users ability to resize a panel. This method call is just a suggestion + * and the GUI toolkit may totally ignore this call. + */ + public void setResizable(boolean aBool) + { + childComp.setResizable(aBool); + } + + @Override + public void componentResized(ComponentEvent aEvent) + { + ; // Nothing to do + } + + @Override + public void componentMoved(ComponentEvent e) + { + if (e.getSource() == childComp) + refLayout.setComponentConstraints(childComp, "pos " + childComp.getLocation().x + " " + childComp.getLocation().y); + } + + @Override + public void componentShown(ComponentEvent e) + { + ; // Nothing to do + } + + @Override + public void componentHidden(ComponentEvent e) + { + ; // Nothing to do + } + + @Override + public void setVisible(boolean isVisible) + { + JLayeredPane layeredPane; + KeyboardFocusManager aManager; + + // Initialize the GUI if it has not been initialed + // Once initialized rootPane != null + if (rootPane == null) + initializeGui(); + + super.setVisible(isVisible); + + // Activate/Deactive managing the focus + setFocusCycleRoot(isVisible); + setFocusTraversalPolicyProvider(isVisible); + + // Bail if this GlassPane is no longer visible + if (isVisible == false) + return; + + // Ensure this GlassPane is at the top + layeredPane = rootPane.getLayeredPane(); + layeredPane.moveToFront(this); + + // Clear out the old focus (from the underlay component) + aManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + aManager.clearGlobalFocusOwner(); + + // Wait and ensure that the GlassPane is visible, before we request the focus + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + Component aComponent; + + aComponent = myFocusPolicy.getDefaultComponent(null); + if (aComponent != null) + aComponent.requestFocusInWindow(); + + repaint(); + } + }); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + childComp.zioReadRaw(aStream); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + childComp.zioWriteRaw(aStream); + } + + /** + * Sets up the GUI for the GlassPane + */ + protected void initializeGui() + { + Set compSet; + JLayeredPane layeredPane; + ComponentTracker aTracker; + Dimension prefDim; + Point prefLoc; + + // Ensure this method is not called twice + if (rootPane != null) + throw new RuntimeException("GlassPane.initializeGui() has been already called."); + + // Retrieve the associated rootPane and layeredPane + rootPane = GuiUtil.getRootPaneContainer(parentComp); + + // Add the GlassPane into the layeredPane + layeredPane = rootPane.getLayeredPane(); + layeredPane.setLayer(this, JLayeredPane.PALETTE_LAYER); + layeredPane.add(this); + setSize(layeredPane.getSize()); +// layeredPane.validate(); + layeredPane.revalidate(); + + // Determine initial location and size of the child component + prefDim = childComp.getPrefDim(); + if (prefDim == null) + prefDim = childComp.getPreferredSize(); + + prefLoc = childComp.getPrefLoc(); + if (prefLoc == null) + prefLoc = new Point(getWidth()/2 - prefDim.width/2, getHeight()/2 - prefDim.height/2); + +// System.out.println("prefLoc: " + prefLoc + " prefDim: " + prefDim); + childComp.setLocation(prefLoc); + childComp.setSize(prefDim); +//childComp.revalidate(); +//layeredPane.revalidate(); +//this.validate(); +//this.revalidate(); + + // Setup the focus policy to consist of any child components of type + // AbstractButton, JComboBox, JTextField, JTable + compSet = Sets.newLinkedHashSet(); + Class[] classArr = {AbstractButton.class, JComboBox.class, JTextField.class, JTable.class}; + GuiUtil.locateAllSubComponents(childComp, compSet, classArr); + + myFocusPolicy = new CustomFocusTraversalPolicy(); + for (Component aItem : compSet) + myFocusPolicy.addComponent(aItem); + + setFocusCycleRoot(true); + setFocusTraversalPolicyProvider(true); + setFocusTraversalPolicy(myFocusPolicy); + + // Set up a ComponentTracker to keep this GlassPane linked to the + // appropriate Components of interest + aTracker = new ComponentTracker(this); + aTracker.setHiddenTracker((Component)rootPane); + aTracker.setResizedTracker(layeredPane); + } + + @Override + protected void paintComponent(Graphics g) + { + g.setColor(fillColor); + g.fillRect(0, 0, getWidth(), getHeight()); + } + + /** + * Utility method to help with debugging + */ + protected void debugMsg(Component aComp, String aStr) + { + if (aComp == childComp) + System.out.println("displayPane being " + aStr + "..."); + else if (aComp == rootPane) + System.out.println("rootPane being " + aStr + "..."); + else + System.out.println("Undefined object being " + aStr + "..."); + } + +} + + +/** + * Utility class to capture all mouse interactions and redispatch the events only + * to the subcomponents associated with the container. + */ +class GlassPaneListener extends MouseInputAdapter +{ + private Component glassPane; + private Container contentPane; + + public GlassPaneListener(Component aGlassPane, Container aContentPane) + { + glassPane = aGlassPane; + contentPane = aContentPane; + } + + @Override + public void mouseMoved(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mouseDragged(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mouseClicked(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mouseEntered(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mouseExited(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mousePressed(MouseEvent e) + { + redispatchMouseEvent(e, false); + } + + @Override + public void mouseReleased(MouseEvent e) + { + redispatchMouseEvent(e, true); + } + + //A more finished version of this method would + //handle mouse-dragged events specially. + private void redispatchMouseEvent(MouseEvent e, boolean repaint) + { + + Point glassPanePoint = e.getPoint(); + Container container = contentPane; + Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane); + + //The mouse event is probably over the content pane. + //Find out exactly which component it's over. + Component component; + + component = SwingUtilities.getDeepestComponentAt(container, containerPoint.x, containerPoint.y); + if (component != null) + { + //Forward events over the to the component + dispatchEvent(component, e); + } + + // Update the glass pane if requested. + if (repaint) + glassPane.repaint(); + } + + private void dispatchEvent(Component aComponent, MouseEvent aEvent) + { + Point aPt; + + if (aComponent == null || aEvent == null) + return; + + aPt = SwingUtilities.convertPoint(glassPane, aEvent.getPoint(), aComponent); + if (aPt == null) + return; + + aComponent.dispatchEvent(new MouseEvent(aComponent, aEvent.getID(), aEvent.getWhen(), aEvent.getModifiers(), + aPt.x, aPt.y, aEvent.getClickCount(), aEvent.isPopupTrigger())); + } + +} diff --git a/src/glum/gui/panel/GlassPaneAncient.java b/src/glum/gui/panel/GlassPaneAncient.java new file mode 100644 index 0000000..abe2e68 --- /dev/null +++ b/src/glum/gui/panel/GlassPaneAncient.java @@ -0,0 +1,56 @@ +package glum.gui.panel; + +import glum.gui.GuiUtil; + +import java.awt.*; +import javax.swing.*; + +import net.miginfocom.swing.MigLayout; + +public class GlassPaneAncient extends JComponent +{ + // Communicator vars + protected Component refParent; + protected JComponent displayPane; + + /** + * Constructor + */ + public GlassPaneAncient(Component aRefParent, JComponent aDisplayPane) + { + // Communicator vars + refParent = aRefParent; + displayPane = aDisplayPane; + + // Build the GUI + setLayout(new MigLayout("", "0[center,grow]0", "0[center,grow]0")); + add(aDisplayPane, "span 1"); + + GlassPaneListener aListener = new GlassPaneListener(this, aDisplayPane); + addMouseListener(aListener); + addMouseMotionListener(aListener); + } + + @Override + public void setVisible(boolean isVisible) + { + RootPaneContainer rootPane; + + // Update the RootPane to use this GlassPane + rootPane = GuiUtil.getRootPaneContainer(refParent); + if (isVisible == true) + rootPane.setGlassPane(this); +// else if (isVisible == false && rootPane.getGlassPane() == this) +// rootPane.setGlassPane(null); + + super.setVisible(isVisible); + } + + @Override + protected void paintComponent(Graphics g) + { + g.setColor(new Color(192, 192, 192, 192)); + g.fillRect(0, 0, getWidth(), getHeight()); + } + +} diff --git a/src/glum/gui/panel/GlassPanel.java b/src/glum/gui/panel/GlassPanel.java new file mode 100644 index 0000000..4d737cd --- /dev/null +++ b/src/glum/gui/panel/GlassPanel.java @@ -0,0 +1,111 @@ +package glum.gui.panel; + +import glum.gui.GuiUtil; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.awt.Component; +import java.awt.Dimension; +import java.io.IOException; + +public abstract class GlassPanel extends GPanel implements ZioRaw +{ + // State vars + protected Component myGlassPane; + + public GlassPanel(Component aRefParent) + { + this(aRefParent, PaneType.GlassPanel); + } + + public GlassPanel(Component aRefParent, PaneType aType) + { + super(); + + if (aType == PaneType.GlassPanel) + myGlassPane = new GlassPane(aRefParent, this); + else if (aType == PaneType.GlassPanelAncient) + myGlassPane = new GlassPaneAncient(aRefParent, this); + else + myGlassPane = new StandardPane(aRefParent, this); + } + + /** + * Call this method to change the end-user's ability to resize this component. Note this call is just a suggestion + * and may be totally ignored by the GUI toolkit. + */ + public void setResizable(boolean aBool) + { + if (myGlassPane instanceof GlassPane) + ((GlassPane)myGlassPane).setResizable(false); + } + + /** + * Call this method if you want the GlassPanel to block until the panel has + * been hidden. + */ + public void setVisibleAsModal() + { + setVisible(true); + + if (myGlassPane instanceof GlassPane) + GuiUtil.modalWhileVisible(myGlassPane); + else + GuiUtil.modalWhileVisible(getParent()); + } + + @Override + public void setSize(Dimension aDim) + { + if (myGlassPane instanceof GlassPane) + ((GlassPane)myGlassPane).childComp.setSize(aDim); + else + super.setSize(aDim); + } + + @Override + public void setSize(int width, int height) + { + setSize(new Dimension(width, height)); + } + + @Override + public void setVisible(boolean isVisible) + { +// // Ensure this panel is visible +// if (isVisible() == false) +// super.setVisible(isVisible); + + myGlassPane.setVisible(isVisible); + } + + @Override + public void repaint() + { + if (myGlassPane == null) + return; + + if (myGlassPane.isVisible() == true) + myGlassPane.repaint(); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + if (myGlassPane instanceof ZioRaw) + ((ZioRaw)myGlassPane).zioReadRaw(aStream); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + aStream.writeVersion(0); + + if (myGlassPane instanceof ZioRaw) + ((ZioRaw)myGlassPane).zioWriteRaw(aStream); + } + +} diff --git a/src/glum/gui/panel/PaneType.java b/src/glum/gui/panel/PaneType.java new file mode 100644 index 0000000..8372b67 --- /dev/null +++ b/src/glum/gui/panel/PaneType.java @@ -0,0 +1,8 @@ +package glum.gui.panel; + +public enum PaneType +{ + GlassPanelAncient, + GlassPanel, + StandardPanel +} diff --git a/src/glum/gui/panel/ShadePane.java b/src/glum/gui/panel/ShadePane.java new file mode 100644 index 0000000..b4054ea --- /dev/null +++ b/src/glum/gui/panel/ShadePane.java @@ -0,0 +1,99 @@ +package glum.gui.panel; + +import glum.gui.GuiUtil; +import java.awt.*; +import javax.swing.*; + +public class ShadePane extends JComponent +{ + // Communicator vars + protected Component targComp; + protected RootPaneContainer rootPane; + + // State vars + Color refColor; + + /** + * Constructor + */ + public ShadePane(Component aComp) + { + targComp = aComp; + rootPane = null; + + refColor = new Color(192, 192, 192, 128); + } + + public void setColor(Color aColor) + { + refColor = aColor; + } + + @Override + public void setVisible(boolean isVisible) + { + JLayeredPane layeredPane; + + // Initialize the GUI if it has not been initialed + // Once initialized rootPane != null + if (rootPane == null) + initializeGui(); + + super.setVisible(isVisible); + + // Bail if this ShadePane is no longer visible + if (isVisible == false) + return; + + // Ensure this ShadePane is at the top + layeredPane = rootPane.getLayeredPane(); + layeredPane.moveToFront(this); + } + + /** + * Initialize and sets up the ShadePane. This method should be called only on the first time it is set visible. + */ + protected void initializeGui() + { + JLayeredPane layeredPane; + ComponentTracker aTracker; + + // Ensure this method is not called twice + if (rootPane != null) + throw new RuntimeException("ShadePane.initializeGui() has been already called."); + + // Retrieve the associated rootPane and layeredPane + rootPane = GuiUtil.getRootPaneContainer(targComp); + + // Add the GlassPane into the layeredPane + layeredPane = rootPane.getLayeredPane(); + layeredPane.setLayer(this, JLayeredPane.PALETTE_LAYER); + layeredPane.add(this); + setLocation(targComp.getLocation()); + setSize(targComp.getSize()); + layeredPane.validate(); + + // Set up a ComponentTracker to keep this ShadePane linked to the rootPane + aTracker = new ComponentTracker(this); + aTracker.setResizedTracker(rootPane.getContentPane()); + + // Set up our initial size + setSize(rootPane.getContentPane().getSize()); + } + + @Override + protected void paintComponent(Graphics g) + { + Point pt1, pt2; + + // Transform the location of the targComp to our screen coordinates + pt1 = targComp.getLocation(); + pt2 = SwingUtilities.convertPoint(targComp.getParent(), pt1, this); + + g.setColor(refColor); + + g.setClip(null); + g.fillRect(pt2.x, pt2.y, targComp.getWidth(), targComp.getHeight()); + } + +} diff --git a/src/glum/gui/panel/StandardPane.java b/src/glum/gui/panel/StandardPane.java new file mode 100644 index 0000000..8fed0d9 --- /dev/null +++ b/src/glum/gui/panel/StandardPane.java @@ -0,0 +1,103 @@ +package glum.gui.panel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; + +import javax.swing.JComponent; +import javax.swing.RootPaneContainer; +import glum.gui.GuiUtil; +import glum.gui.panel.CustomFocusTraversalPolicy; +import net.miginfocom.swing.MigLayout; + +public class StandardPane extends JComponent +{ + // Communicator vars + protected Component refParent; + protected JComponent displayPane; + + protected RootPaneContainer rootPane; + + // State vars + protected Color fillColor; + protected CustomFocusTraversalPolicy myFocusPolicy; + + /** + * Constructor + */ + public StandardPane(Component aRefParent, JComponent aDisplayPane) + { + // Communicator vars + refParent = aRefParent; + displayPane = aDisplayPane; + + // Build the GUI + setLayout(new MigLayout("", "0[center,fill]0", "0[center,fill]0")); + add(aDisplayPane, "span 1"); + + rootPane = null; + fillColor = new Color(96, 96, 96, 96); + } + + /** + * Set in an alternative shade color. + */ + public void setFillColor(Color aColor) + { + fillColor = aColor; + } + + @Override + public void setVisible(boolean isVisible) + { + // Initialize the GUI if it has not been initialed + // Once initialized rootPane != null + if (rootPane == null) + initializeGui(); + + ((Component)rootPane).setVisible(isVisible); + } + + /** + * Sets up the GlassPane + */ + protected void initializeGui() + { + ComponentTracker aTracker; + + // Ensure this method is not called twice + if (rootPane != null) + throw new RuntimeException("GlassPane.initializeGui() has been already called."); + + // Retrieve the associated rootPane and layeredPane + if (refParent == null) + rootPane = GuiUtil.getRootPaneContainer(displayPane); + else + rootPane = GuiUtil.getRootPaneContainer(refParent); + + // Set up a ComponentTracker to keep this ShadePane linked to the rootPane + aTracker = new ComponentTracker(this); + aTracker.setResizedTracker((Component)rootPane); + } + + @Override + protected void paintComponent(Graphics g) + { + g.setColor(fillColor); + g.fillRect(0, 0, getWidth(), getHeight()); + } + + /** + * Utility method to help with debugging + */ + protected void debugMsg(Component aComp, String aStr) + { + if (aComp == displayPane) + System.out.println("displayPane being " + aStr + "..."); + else if (aComp == rootPane) + System.out.println("rootPane being " + aStr + "..."); + else + System.out.println("Undefined object being " + aStr + "..."); + } + +} diff --git a/src/glum/gui/panel/WaftPanel.java b/src/glum/gui/panel/WaftPanel.java new file mode 100644 index 0000000..b353959 --- /dev/null +++ b/src/glum/gui/panel/WaftPanel.java @@ -0,0 +1,243 @@ +package glum.gui.panel; + +import glum.gui.panel.nub.HorizontalNub; +import glum.util.MathUtil; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; +import glum.zio.util.ZioUtil; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.io.IOException; + +import javax.swing.JComponent; + +import net.miginfocom.swing.MigLayout; + +/** + * Panel which provides decorative controls to allow a panel to be moved or resized. This is especially useful for + * wrapping a component that will be thrown into a JLayered pane of sorts. Note it is assumed that WaftPanel is + * contained in a LayoutManager of type MigLayout. + *

    + * Perhaps, this class should be rewritten so that it draws the move, resize borders, and manually listens to them + * rather than delegating to the Nub classes. The current implementation is slightly inefficient, but probably more + * flexible as it allows other custom Nubs to be swapped in. + */ +public class WaftPanel extends JComponent implements ZioRaw, ComponentListener +{ + // Gui vars + protected Component childComp; + protected JComponent parentComp; + protected HorizontalNub nMoveComp, sMoveComp; + + // State vars + protected Dimension prefDim; + protected Point prefLoc; + protected double aspectRatio; + + public WaftPanel(Component aChildComp, JComponent aParentComp, boolean topNubOnly) + { + super(); + + childComp = aChildComp; + parentComp = aParentComp; + + prefDim = null; + prefLoc = null; + + // Embed the childComp into this WaftPanel and surround with the various nub handles + setLayout(new MigLayout("", "0[grow,fill]0[]0", "0[]0[grow,fill]0[]0")); + + nMoveComp = new HorizontalNub(parentComp, this, true); + sMoveComp = new HorizontalNub(parentComp, this, false); + add("wrap", nMoveComp); + add("growx,growy,wrap", aChildComp); + if (topNubOnly == false) + add("wrap", sMoveComp); + + // Register to listen to components events associated with the parent + parentComp.addComponentListener(this); + } + + public WaftPanel(Component aChildComp, JComponent aParentComp) + { + this(aChildComp, aParentComp, false); + } + + + /** + * Change whether the user can move the WaftPanel. Note this will automatically + * disabling the resizing of the WaftPanel. + */ + public void setMovable(boolean aBool) + { + nMoveComp.setVisible(false); + sMoveComp.setVisible(false); + } + + /** + * Changes whether the associated nubs will provide resize controls. + */ + public void setResizable(boolean aBool) + { + nMoveComp.setResizable(aBool); + sMoveComp.setResizable(aBool); + } + + /** + * Returns the current targeted and locked dimension + */ + public Dimension getPrefDim() + { + if (prefDim == null) + return null; + + return new Dimension(prefDim); + } + + /** + * Returns the current targeted and locked location + */ + public Point getPrefLoc() + { + if (prefLoc == null) + return null; + + return new Point(prefLoc); + } + + @Override + public void setLocation(Point aLoc) + { + // Update the preferred location + prefLoc = new Point(aLoc); + + setLocationAndLock(aLoc); + } + + @Override + public void setLocation(int x, int y) + { + setLocation(new Point(x, y)); + } + + @Override + public void setSize(Dimension aDim) + { + // Update the preferred location + prefDim = new Dimension(aDim); + + super.setSize(aDim); + } + + @Override + public void componentResized(ComponentEvent aEvent) + { + Point targLoc; + Dimension targDim; + Dimension minDim; + + Dimension parentDim; + + // Respond only to resizes of our parent + if (aEvent.getComponent() != parentComp) + return; + + // Record the current WaftPanels as preferences if they have not been recorded + if (prefDim == null) + prefDim = getSize(); + + if (prefLoc == null) + prefLoc = getLocation(); + + // Retrieve the targDim, targLoc, and the minDim + minDim = getMinimumSize(); + targDim = new Dimension(prefDim); + targLoc = new Point(prefLoc); + + // Ensure the targDim is no larger than parentDim + parentDim = parentComp.getSize(); + targDim.width = MathUtil.boundRange(0, parentDim.width, targDim.width); + targDim.height = MathUtil.boundRange(0, parentDim.height, targDim.height); + + // Attempt to force ourself into the constraint of the parentComp + MathUtil.forceConstraints(targLoc, targDim, 0, 0, parentComp.getWidth(), parentComp.getHeight(), new Dimension(0, 0), true); + + // Ensure the minimum constraints are not violated + targDim.width = MathUtil.boundRange(minDim.width, targDim.width, targDim.width); + targDim.height = MathUtil.boundRange(minDim.height, targDim.height, targDim.height); + + // Update the waftPanel with the target transformations + setLocationAndLock(targLoc); + setPreferredSize(targDim); + super.setSize(targDim); + + parentComp.revalidate(); + } + + @Override + public void componentMoved(ComponentEvent e) + { + ; // Nothing to do + } + + @Override + public void componentShown(ComponentEvent e) + { + ; // Nothing to do + } + + @Override + public void componentHidden(ComponentEvent e) + { + ; // Nothing to do + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + prefDim = ZioUtil.readDimension(aStream); + prefLoc = ZioUtil.readPoint(aStream); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + aStream.writeVersion(0); + + ZioUtil.writeDimension(aStream, prefDim); + ZioUtil.writePoint(aStream, prefLoc); + } + + /** + * Helper method to set the location of this WaftPanel relative to its parent. + *

    + * Adjustments are made to our constraints associated with the layout manager. This is needed so that when the layout + * manager is (re)validated any movement or resize changes made will stick. Note we assume that we are placed in a + * MigLayout manager. + */ + private void setLocationAndLock(Point aLoc) + { + MigLayout rootLayout; + + // Adjust our constraints associated in the layout manager. This is needed so that when the layout manager is + // (re)validated any movement or resize changes made will stick. + // Note we assume that we are placed in a MigLayout manager. + rootLayout = (MigLayout)parentComp.getLayout(); + if (rootLayout == null) + { + super.setLocation(aLoc.x, aLoc.y); + return; + } + + rootLayout.setComponentConstraints(this, "pos " + aLoc.x + "px " + aLoc.y + "px"); + rootLayout.invalidateLayout(null); + } + +} diff --git a/src/glum/gui/panel/generic/GenericCodes.java b/src/glum/gui/panel/generic/GenericCodes.java new file mode 100644 index 0000000..56a47de --- /dev/null +++ b/src/glum/gui/panel/generic/GenericCodes.java @@ -0,0 +1,11 @@ +package glum.gui.panel.generic; + +public interface GenericCodes +{ + // Constants + public static final int ID_UNDEFINED = 0; + public static final int ID_UPDATE = 10; + public static final int ID_ACCEPT = 11; + public static final int ID_CANCEL = 12; + +} diff --git a/src/glum/gui/panel/generic/LocationPanel.java b/src/glum/gui/panel/generic/LocationPanel.java new file mode 100644 index 0000000..c3924cc --- /dev/null +++ b/src/glum/gui/panel/generic/LocationPanel.java @@ -0,0 +1,278 @@ +package glum.gui.panel.generic; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GTextField; +import glum.gui.panel.GlassPanel; +import glum.io.Loader; +import glum.io.LoaderInfo; +import glum.unit.ByteUnit; +import glum.unit.Unit; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; + +import net.miginfocom.swing.MigLayout; + +public class LocationPanel extends GlassPanel implements ActionListener, GenericCodes +{ + // Constants + public static final Color warnColor = new Color(128, 0, 0); + + // GUI vars + protected JLabel titleL, locationL, infoL, warnL; + protected JButton cancelB, acceptB, fileB; + protected JTextArea instrTA; + protected GTextField locationTF; + + // State vars + protected LoaderInfo loaderInfo; + protected long minFreeSpace; + protected boolean isAccepted; + + public LocationPanel(Component aParent) + { + super(aParent); + + loaderInfo = new LoaderInfo(); + minFreeSpace = 0; + isAccepted = false; + + // Build the actual GUI + buildGuiArea(); + setPreferredSize(new Dimension(300, getPreferredSize().height)); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(cancelB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(acceptB)); + FocusUtil.addFocusKeyBinding(locationTF, "ENTER", new ClickAction(acceptB)); + } + + /** + * Returns the input of the user. + */ + public File getInput() + { + if (isAccepted == false) + return null; + + return new File(locationTF.getText()); + } + + /** + * Sets in the instruction + */ + public void setInstruction(String aMsg) + { + instrTA.setText(aMsg); + } + + /** + * Sets in the minimum free space required + */ + public void setMinFreeSpace(long aSize) + { + minFreeSpace = aSize; + } + + /** + * Sets in the title + */ + public void setTitle(String aTitle) + { + titleL.setText(aTitle); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == cancelB) + { + isAccepted = false; + setVisible(false); + notifyListeners(this, ID_CANCEL, "Cancel"); + } + else if (source == acceptB) + { + isAccepted = true; + setVisible(false); + notifyListeners(this, ID_ACCEPT, "Accept"); + } + else if (source == locationTF) + { + updateGui(); + } + else if (source == fileB) + { + File aFile; + + // Retrieve the path to load + aFile = Loader.queryUserForPath(loaderInfo, getParent(), "Select target folder", true); + if (aFile != null) + locationTF.setValue(aFile.getAbsolutePath()); + + updateGui(); + } + + } + + @Override + public void setVisible(boolean aBool) + { + // Reset the GUI + if (aBool == true) + { + isAccepted = false; + locationTF.setText(""); + updateGui(); + } + + super.setVisible(aBool); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + super.zioReadRaw(aStream); + + aStream.readVersion(0); + + loaderInfo.zioReadRaw(aStream); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + super.zioWriteRaw(aStream); + + aStream.writeVersion(0); + + loaderInfo.zioWriteRaw(aStream); + } + + /** + * Forms the actual dialog GUI + */ + protected void buildGuiArea() + { + JScrollPane tmpPane; + Font aFont; + String aStr; + + setLayout(new MigLayout("", "[][][grow]", "[][grow,50::][]")); + aFont = (new JTextField()).getFont(); + + // Title Area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Instruction area + instrTA = GuiUtil.createUneditableTextArea(2, 0); + tmpPane = new JScrollPane(instrTA); + tmpPane.setBorder(null); + add(tmpPane, "growx,growy,span,wrap"); + + // Location area + fileB = GuiUtil.createJButton("...", this); + locationL = new JLabel("Location:"); + locationTF = new GTextField(this); + add(fileB, "w 20!,h 20!"); + add(locationL); + add(locationTF, "growx,span,wrap"); + + // Info area + aStr = "Please specify the disk location where the catalog should be constructed.."; + infoL = GuiUtil.createJLabel(aStr, aFont); + add(infoL, "growx,span,wrap"); + + // Warn area + aStr = ""; + warnL = GuiUtil.createJLabel(aStr, aFont); + warnL.setForeground(warnColor); + add(warnL, "growx,h 20!,span,wrap"); + + // Control area + cancelB = GuiUtil.createJButton("Cancel", this, aFont); + acceptB = GuiUtil.createJButton("Accept", this, aFont); + add(cancelB, "align right,span,split 2"); + add(acceptB, ""); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + /** + * Utility method to update the various GUI components (most likely infoL, acceptB) based on the current inputTF. + */ + protected void updateGui() + { + File destPath, rootPath; + String infoStr, warnStr; + Unit diskUnit; + boolean isValid; + long freeBytes; + + // Retrieve the folder + destPath = null; + if (locationTF.getText().isEmpty() == false) + destPath = new File(locationTF.getText()); + + // Retrieve the root folder + rootPath = destPath; + while (rootPath != null) + { + if (rootPath.isDirectory() == true) + break; + + rootPath = rootPath.getParentFile(); + } + + // Test the validity of the location + isValid = false; + if (rootPath == null) + { + infoStr = "Free space: ---"; + warnStr = "Invalid location."; + } + else + { + diskUnit = new ByteUnit(2); + freeBytes = rootPath.getFreeSpace(); + infoStr = "Free space: " + diskUnit.getString(freeBytes); + if (minFreeSpace > 0) + infoStr += " Required space: " + diskUnit.getString(minFreeSpace); + + warnStr = ""; + if (rootPath.canWrite() == false) + warnStr = "No write permission at location."; + else if (freeBytes < minFreeSpace && minFreeSpace > 0) + warnStr = "Not enough free space on disk. Minimun required: " + diskUnit.getString(minFreeSpace); + else + isValid = true; + } + + // Update the components + infoL.setText(infoStr); + warnL.setText(warnStr); + acceptB.setEnabled(isValid); + } + +} diff --git a/src/glum/gui/panel/generic/MessagePanel.java b/src/glum/gui/panel/generic/MessagePanel.java new file mode 100644 index 0000000..38c1f56 --- /dev/null +++ b/src/glum/gui/panel/generic/MessagePanel.java @@ -0,0 +1,117 @@ +package glum.gui.panel.generic; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.panel.GlassPanel; + +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; + +import net.miginfocom.swing.MigLayout; + +public class MessagePanel extends GlassPanel implements ActionListener, GenericCodes +{ + // GUI vars + protected JLabel titleL; + protected JTextArea infoTA; + protected JButton closeB; + + public MessagePanel(Component aParent, String aTitle, int sizeX, int sizeY) + { + super(aParent); + + buildGuiArea(); + setSize(sizeX, sizeY); + setTitle(aTitle); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(closeB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(closeB)); + } + + public MessagePanel(Component aParent) + { + this(aParent, "Untitled", 275, 350); + } + + public MessagePanel(Component aParent, String aTitle) + { + this(aParent, aTitle, 275, 350); + } + + /** + * Sets the message of the PromptPanel + */ + public void setInfo(String aStr) + { + infoTA.setText(aStr); + } + + /** + * Sets the title of this PromptPanel + */ + public void setTitle(String aTitle) + { + titleL.setText(aTitle); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == closeB) + { + setVisible(false); + notifyListeners(this, ID_CANCEL, "Close"); + } + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea() + { + JScrollPane tmpScrollPane; + Font aFont; + Border aBorder; + + setLayout(new MigLayout("", "[right][grow][]", "[][grow][]")); + aFont = (new JTextField()).getFont(); + + // Title Area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Info area + infoTA = new JTextArea("No status", 3, 0); + infoTA.setEditable(false); +// infoTA.setOpaque(false); + infoTA.setLineWrap(true); + infoTA.setWrapStyleWord(true); + + tmpScrollPane = new JScrollPane(infoTA); +// tmpScrollPane.setBorder(null); + add(tmpScrollPane, "growx,growy,span,wrap"); + + // Control area + closeB = GuiUtil.createJButton("Close", this, aFont); + add(closeB, "skip 2,span 1"); + + // Border + aBorder = new BevelBorder(BevelBorder.RAISED); + setBorder(aBorder); + } + +} diff --git a/src/glum/gui/panel/generic/PromptPanel.java b/src/glum/gui/panel/generic/PromptPanel.java new file mode 100644 index 0000000..e373fe3 --- /dev/null +++ b/src/glum/gui/panel/generic/PromptPanel.java @@ -0,0 +1,149 @@ +package glum.gui.panel.generic; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.panel.GlassPanel; + +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; + +import net.miginfocom.swing.MigLayout; + +public class PromptPanel extends GlassPanel implements ActionListener, GenericCodes +{ + // GUI vars + protected JLabel titleL; + protected JTextArea infoTA; + protected JButton cancelB, acceptB; + + // State vars + protected boolean isAccepted; + + public PromptPanel(Component aParent, String aTitle, int sizeX, int sizeY) + { + super(aParent); + + isAccepted = false; + + buildGuiArea(); + setSize(sizeX, sizeY); + setTitle(aTitle); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(cancelB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(acceptB)); + } + + public PromptPanel(Component aParent) + { + this(aParent, "Untitled", 275, 350); + } + + public PromptPanel(Component aParent, String aTitle) + { + this(aParent, aTitle, 275, 350); + } + + /** + * Returns true if the prompt was accepted + */ + public boolean isAccepted() + { + return isAccepted; + } + + /** + * Sets the message of the PromptPanel + */ + public void setInfo(String aStr) + { + infoTA.setText(aStr); + } + + /** + * Sets the title of this PromptPanel + */ + public void setTitle(String aTitle) + { + titleL.setText(aTitle); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == cancelB) + { + isAccepted = false; + setVisible(false); + notifyListeners(this, ID_CANCEL, "Cancel"); + } + else if (source == acceptB) + { + isAccepted = true; + setVisible(false); + notifyListeners(this, ID_ACCEPT, "Accept"); + } + } + + @Override + public void setVisible(boolean isVisible) + { + // Reset the panel + if (isVisible == true) + isAccepted = false; + + super.setVisible(isVisible); + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea() + { + JScrollPane tmpScrollPane; + Font aFont; + Border aBorder; + + setLayout(new MigLayout("", "[right][grow][][]", "[][grow][]")); + aFont = (new JTextField()).getFont(); + + // Title Area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Info area + infoTA = new JTextArea("No status", 3, 0); + infoTA.setEditable(false); +// infoTA.setOpaque(false); + infoTA.setLineWrap(true); + infoTA.setWrapStyleWord(true); + + tmpScrollPane = new JScrollPane(infoTA); +// tmpScrollPane.setBorder(null); + add(tmpScrollPane, "growx,growy,span,wrap"); + + // Control area + cancelB = GuiUtil.createJButton("Cancel", this, aFont); + acceptB = GuiUtil.createJButton("Accept", this, aFont); + add(cancelB, "skip 2"); + add(acceptB, ""); + + // Border + aBorder = new BevelBorder(BevelBorder.RAISED); + setBorder(aBorder); + } + +} diff --git a/src/glum/gui/panel/generic/SimplePromptPanel.java b/src/glum/gui/panel/generic/SimplePromptPanel.java new file mode 100644 index 0000000..9aef442 --- /dev/null +++ b/src/glum/gui/panel/generic/SimplePromptPanel.java @@ -0,0 +1,116 @@ +package glum.gui.panel.generic; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.border.*; + +import glum.gui.*; +import glum.gui.action.ClickAction; +import glum.gui.panel.GlassPanel; +import glum.gui.panel.generic.GenericCodes; +import net.miginfocom.swing.MigLayout; + +public class SimplePromptPanel extends GlassPanel implements ActionListener, GenericCodes +{ + // GUI vars + protected JLabel titleL, messageL; + protected JButton cancelB, acceptB; + + // State vars + protected boolean isAccepted; + + public SimplePromptPanel(Component aParent) + { + super(aParent); + + isAccepted = false; + + // Build the actual GUI + buildGuiArea(); + setPreferredSize(new Dimension(300, getPreferredSize().height)); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(cancelB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(acceptB)); + } + + /** + * Returns true if the user accepted the prompt + */ + public boolean isAccepted() + { + return isAccepted; + } + + /** + * Sets in the title of this PromptPanel + */ + public void setTitle(String aStr) + { + titleL.setText(aStr); + } + + /** + * Sets in the informational message of this PromptPanel + */ + public void setMessage(String aStr) + { + messageL.setText(aStr); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == cancelB) + { + isAccepted = false; + setVisible(false); + notifyListeners(this, ID_CANCEL, "Cancel"); + } + else if (source == acceptB) + { + isAccepted = true; + setVisible(false); + notifyListeners(this, ID_ACCEPT, "Accept"); + } + } + + @Override + public void setVisible(boolean aBool) + { + isAccepted = false; + super.setVisible(aBool); + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea() + { + Font aFont; + + setLayout(new MigLayout("", "[right][grow][][]", "[][][20!][]")); + aFont = (new JTextField()).getFont(); + + // Title Area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Message area + messageL = GuiUtil.createJLabel("Message", aFont); + add(messageL, "growx,span,wrap"); + + // Control area + cancelB = GuiUtil.createJButton("Cancel", this, aFont); + acceptB = GuiUtil.createJButton("Accept", this, aFont); + add(cancelB, "skip 2"); + add(acceptB, ""); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + +} diff --git a/src/glum/gui/panel/generic/TextInputPanel.java b/src/glum/gui/panel/generic/TextInputPanel.java new file mode 100644 index 0000000..e79c8d3 --- /dev/null +++ b/src/glum/gui/panel/generic/TextInputPanel.java @@ -0,0 +1,153 @@ +package glum.gui.panel.generic; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; +import javax.swing.border.BevelBorder; +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GTextField; +import glum.gui.panel.GlassPanel; +import net.miginfocom.swing.MigLayout; + +public abstract class TextInputPanel extends GlassPanel implements ActionListener, GenericCodes +{ + // Constants + public static final Color warnColor = new Color(128, 0, 0); + + // GUI vars + protected JLabel titleL, inputL, infoL; + protected JButton cancelB, acceptB; + protected GTextField inputTF; + + // State vars + protected boolean isAccepted; + + /** + * Constructor + */ + public TextInputPanel(Component aParent) + { + super(aParent); + + isAccepted = false; + + // Build the actual GUI + buildGuiArea(); + setPreferredSize(new Dimension(300, getPreferredSize().height)); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(cancelB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(acceptB)); + FocusUtil.addFocusKeyBinding(inputTF, "ENTER", new ClickAction(acceptB)); + } + + /** + * Returns the input of the user. + */ + public String getInput() + { + if (isAccepted == false) + return null; + + return inputTF.getText(); + } + + /** + * Sets in aStr as the default input + */ + public void setInput(String aStr) + { + inputTF.setValue(aStr); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == cancelB) + { + isAccepted = false; + setVisible(false); + notifyListeners(this, ID_CANCEL, "Cancel"); + } + else if (source == acceptB) + { + isAccepted = true; + setVisible(false); + notifyListeners(this, ID_ACCEPT, "Accept"); + } + else if (source == inputTF) + { + updateGui(); + } + } + + @Override + public void setVisible(boolean aBool) + { + if (aBool == true) + isAccepted = false; +// resetGui(); + + super.setVisible(aBool); + } + + /** + * Forms the actual dialog GUI + */ + protected void buildGuiArea() + { + Font aFont; + String aStr; + + setLayout(new MigLayout("", "[right][grow][][]", "[][][20!][]")); + aFont = (new JTextField()).getFont(); + + // Title Area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span 4,wrap"); + + // Source area + inputL = new JLabel("Symbol:"); + inputTF = new GTextField(this); + add(inputL); + add(inputTF, "growx,span 3,wrap"); + + // Warn area + aStr = "Please enter text input."; + infoL = GuiUtil.createJLabel(aStr, aFont); + infoL.setForeground(warnColor); + add(infoL, "growx,span 4,wrap"); + + // Control area + cancelB = GuiUtil.createJButton("Cancel", this, aFont); + acceptB = GuiUtil.createJButton("Accept", this, aFont); + add(cancelB, "skip 2,span 1"); + add(acceptB, "span 1"); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + /** + * Sets the Gui and associated components to the initial state + */ + public void resetGui() + { + isAccepted = false; + inputTF.setText(""); + updateGui(); + } + + /** + * Utility method to update the various GUI components + * (most likely infoL, acceptB) based on the current + * inputTF. + */ + protected abstract void updateGui(); + +} diff --git a/src/glum/gui/panel/itemList/BasicItemHandler.java b/src/glum/gui/panel/itemList/BasicItemHandler.java new file mode 100644 index 0000000..c0cc718 --- /dev/null +++ b/src/glum/gui/panel/itemList/BasicItemHandler.java @@ -0,0 +1,572 @@ +package glum.gui.panel.itemList; + +import glum.gui.panel.itemList.query.QueryAttribute; +import glum.gui.panel.itemList.query.QueryComposer; +import glum.gui.panel.itemList.query.QueryTableCellRenderer; +import glum.unit.Unit; +import glum.unit.UnitListener; +import glum.unit.UnitProvider; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; +import glum.zio.raw.ZioRawUtil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import javax.swing.JTable; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public abstract class BasicItemHandler implements ZioRaw, ItemHandler, UnitListener +{ + protected JTable myOwner; + protected ArrayList fullAttributeList; + protected ArrayList sortedAttributeList; + + /** + * Constructor + */ + public BasicItemHandler(QueryComposer aComposer) + { + this(aComposer.getItems()); + } + + public BasicItemHandler(Collection aQueryAttrList) + { + int evalIndex; + + myOwner = null; + + fullAttributeList = new ArrayList(); + sortedAttributeList = new ArrayList(); + + if (aQueryAttrList != null && aQueryAttrList.isEmpty() == false) + { + evalIndex = 0; + for (QueryAttribute aAttr : aQueryAttrList) + { + // Ensure the model index is appropriately initialized + if (evalIndex != aAttr.modelIndex) + throw new RuntimeException("Improper initialization. Expected Index: " + evalIndex + " Received Index: " + aAttr.modelIndex); + + fullAttributeList.add(aAttr); + sortedAttributeList.add(aAttr); + evalIndex++; + + // Register for the appropriate unit events + aAttr.refUnitProvider.addListener(this); + } + } + + fullAttributeList.trimToSize(); + sortedAttributeList.trimToSize(); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + ArrayList newSortedList; + int numItems, index; + + // Header + aStream.readVersion(0); + + // Payload + ZioRawUtil.readRawList(aStream, fullAttributeList); + + // Reorder the sortedAttributeList based on the serialization + numItems = aStream.readInt(); + newSortedList = Lists.newArrayListWithCapacity(numItems); + for (int c1 = 0; c1 < numItems; c1++) + { + index = aStream.readInt(); + newSortedList.add(fullAttributeList.get(index)); + } + + sortedAttributeList = newSortedList; + sortedAttributeList.trimToSize(); + + // Initialize the table columns + rebuildTableColumns(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + int numItems; + + // Header + aStream.writeVersion(0); + + // Payload + ZioRawUtil.writeRawList(aStream, fullAttributeList); + + // Output the order of the sortedAttributeList + numItems = sortedAttributeList.size(); + aStream.writeInt(numItems); + for (QueryAttribute aAttr : sortedAttributeList) + aStream.writeInt(aAttr.modelIndex); + } + + @Override + public void initialize(JTable aOwner) + { + JTableHeader aTableHeader; + TableColumnModel aTableColumnModel; + TableColumn aTableColumn; + + // This method is only allowed to be called once! + if (myOwner != null) + throw new RuntimeException("QueryItemHandler already initialized!"); + + myOwner = aOwner; + + aTableHeader = myOwner.getTableHeader(); + aTableColumnModel = aTableHeader.getColumnModel(); + + // Customize overall settings + aTableHeader.setReorderingAllowed(false); + + // Grab all of the precomputed columns from the table + // and store with their associated queryAttributes + for (int c1 = 0; c1 < fullAttributeList.size(); c1++) + { + aTableColumn = aTableColumnModel.getColumn(c1); + fullAttributeList.get(c1).assocTableColumn = aTableColumn; + } + + // Rebuild the table columns; Needed so that only the + // visible ones are displayed. Do this only after you + // have grabbed the table columns as aTableColumnModel + // will strictly contain the "visible" ones + rebuildTableColumns(); + } + + @Override + public int getColumnCount() + { + return fullAttributeList.size(); + } + + @Override + public Class getColumnClass(int colNum) + { + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return String.class; + + return fullAttributeList.get(colNum).refClass; + } + + public int getColumnDefaultWidth(int colNum) + { + int defaultSize, minSize; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return -1; + + // Get the default and min size + defaultSize = fullAttributeList.get(colNum).defaultSize; + minSize = fullAttributeList.get(colNum).minSize; + + // Ensure size makes sense + if (defaultSize < minSize) + return minSize; + + return defaultSize; + } + + /** + * getColumnMinWidth + */ +/* public int getColumnMinWidth(int colNum) + { + // Insanity check + if (queryAttributes == null) + return -1; + + if (colNum < 0 && colNum >= queryAttributes.length) + return -1; + + return queryAttributes[colNum].minSize; + } +*/ + /** + * getColumnMaxWidth + */ + public int getColumnMaxWidth(int colNum) + { + int defaultSize, maxSize; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return -1; + + // Get the default and max size + defaultSize = fullAttributeList.get(colNum).defaultSize; + maxSize = fullAttributeList.get(colNum).maxSize; + + // Ensure size makes sense + if (defaultSize > maxSize && maxSize != -1) + return defaultSize; + + return maxSize; + } + + @Override + public String getColumnLabel(int colNum) + { + QueryAttribute aAttribute; + Unit aUnit; + String aStr; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return ""; + + aAttribute = fullAttributeList.get(colNum); + + // Retrieve the associated unit + aUnit = aAttribute.refUnitProvider.getUnit(); + + // Retrieve the base column label + aStr = aAttribute.label; + + // Append the unit name to the column label + if (aUnit != null && "".equals(aUnit.getLabel(false)) == false) + return aStr + " [" + aUnit.getLabel(false) + "]"; + else + return aStr; + } + + @Override + public Collection getColumnLabels() + { + Collection retSet; + + if (fullAttributeList == null) + return new ArrayList(); + + retSet = new ArrayList(); + for (QueryAttribute aAttribute : fullAttributeList) + { + if (aAttribute != null && aAttribute.label != null) + retSet.add(aAttribute.label); + else + retSet.add(""); + } + + return retSet; + } + + @Override + public boolean isCellEditable(int colNum) + { + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return false; + + if (fullAttributeList.get(colNum).editor == null) + return false; + + return true; + } + + @Override + public boolean isColumnVisible(int colNum) + { + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return false; + + return fullAttributeList.get(colNum).isVisible; + } + + /** + * update + */ + public void update() + { + TableColumn aTableColumn; + QueryTableCellRenderer aRenderer; + Object aObject; + Unit aUnit; + + // Update all the TableColumn renderers with the appropriate unit + for (QueryAttribute aAttribute : fullAttributeList) + { + if (aAttribute.assocTableColumn != null) + { + aTableColumn = aAttribute.assocTableColumn; + aObject = aTableColumn.getCellRenderer(); + if (aObject instanceof QueryTableCellRenderer) + { + aRenderer = (QueryTableCellRenderer)aObject; + + aAttribute.assocTableColumn.setHeaderValue( getColumnLabel(aAttribute.modelIndex) ); + + aUnit = aAttribute.refUnitProvider.getUnit(); + aRenderer.setUnit(aUnit); + } + } + } + } + + @Override + public void unitChanged(UnitProvider aManager, String aKey) + { + JTableHeader aTableHeader; + + update(); + + myOwner.repaint(); + + aTableHeader = myOwner.getTableHeader(); + if (aTableHeader != null) + aTableHeader.repaint(); +/* + for (QueryAttribute aAttribute : queryAttributes) + { + if (aKey.equals(aAttribute.unitKey) == true) + } + + Tile aTile; + + // Update our listPanel to by sync with the active tile + if (aKey.equals("tile.active") == true) + { + aTile = refRegistry.getSingleton(aKey, Tile.class); + + listPanel.removeListSelectionListener(this); + listPanel.selectItem(aTile); + listPanel.addListSelectionListener(this); + } + + updateGui(); +*/ + } + + + +public Unit getUnit(int colNum) +{ + QueryAttribute aAttribute; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return null; + + aAttribute = fullAttributeList.get(colNum); + return aAttribute.refUnitProvider.getUnit(); +} + + +public void setColumnAlignment(int colNum, int aAlignment) +{ + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + fullAttributeList.get(colNum).alignment = aAlignment; +} + +public void setColumnLabel(int colNum, String aLabel) +{ + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + fullAttributeList.get(colNum).label = aLabel; +} + +public void setColumnPosition(int colNum, int aPosition) +{ + // Insanity check + if (colNum < 0 || aPosition < 0 + || colNum >= fullAttributeList.size() + || aPosition >= fullAttributeList.size()) + return; + + sortedAttributeList.remove(fullAttributeList.get(colNum)); + sortedAttributeList.add(aPosition, fullAttributeList.get(colNum)); +} + + +public void setColumnSize(int colNum, int aSize) +{ + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + +System.out.println("[QueryItemHandler.java] Changing size of colNum: " + aSize); + fullAttributeList.get(colNum).defaultSize = aSize; +} + + +public void setColumnSortDir(int colNum, int aSortDir) +{ + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + fullAttributeList.get(colNum).sortDir = aSortDir; +} + + +public void setColumnVisible(int colNum, boolean isVisible) +{ + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + fullAttributeList.get(colNum).isVisible = isVisible; + + // Update our table + if (myOwner != null) + rebuildTableColumns(); +} + + + + + + + + + + /** + * getSortedAttributes + */ + public ArrayList getSortedAttributes() + { + return new ArrayList(sortedAttributeList); + } + + + /** + * moveSortedAttribute + */ + public void moveSortedAttribute(int currIndex, int newIndex) + { + QueryAttribute aItem; + + aItem = sortedAttributeList.get(currIndex); + sortedAttributeList.remove(currIndex); + sortedAttributeList.add(newIndex, aItem); + } + + /** + * Method to reconfigure the columns of the table. This will update the + * display order of the columns as well as the individual relevant + * attributes of the column. Currently supported relevant attributes are + * those defined in the method {@link QueryAttribute#setConfig}. + * + * Note any non specified columns will appear last according to the previous order. + * + * @param orderSet: Ordered set of QueryAttributes with matching modelIndexes + */ + public void setOrderAndConfig(Collection orderArr) + { + Map itemMap; + QueryAttribute workItem; + + // Form a lookup map (modelIndex to attribute) + itemMap = Maps.newLinkedHashMap(); + for (QueryAttribute aItem : sortedAttributeList) + itemMap.put(aItem.modelIndex, aItem); + + // Rebuild the sortedQueryAttribute list to conform with + // - the specified order of orderList + // - synch up relevant attributes + sortedAttributeList.clear(); + for (QueryAttribute aItem: orderArr) + { + workItem = itemMap.remove(aItem.modelIndex); + if (workItem != null) + { + workItem.setConfig(aItem); + sortedAttributeList.add(workItem); + } + } + + sortedAttributeList.addAll(itemMap.values()); + itemMap.clear(); + } + + /** + * initializeTableColumn - Helper method to initialize aTableColumn + * with the actual QueryAttribute properties. + * + * @param colNum: Index into column model + */ + protected void initializeTableColumn(int colNum) + { + TableCellRenderer aRenderer; + QueryAttribute aAttribute; + TableColumn aTableColumn; + String aLabel; + int defaultWidth, maxWidth, minWidth; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + // Get the associated table column + aAttribute = fullAttributeList.get(colNum); + aTableColumn = aAttribute.assocTableColumn; + if (aTableColumn == null) + return; + + // Retrieve settings of interest + aLabel = getColumnLabel(colNum); + defaultWidth = getColumnDefaultWidth(colNum); + maxWidth = getColumnMaxWidth(colNum); + minWidth = aAttribute.minSize; + + // Set up the column's renderer + aRenderer = aAttribute.renderer; + if (aRenderer == null) + aRenderer = new QueryTableCellRenderer(aAttribute); + aTableColumn.setCellRenderer(aRenderer); + + // Set up the column's editor + aTableColumn.setCellEditor(aAttribute.editor); + + // Set up the column's size attributes + aTableColumn.setMinWidth(minWidth); + aTableColumn.setMaxWidth(maxWidth); + aTableColumn.setPreferredWidth(defaultWidth); + + // Set up the column header + aTableColumn.setHeaderValue(aLabel); + } + +// TODO -> This should probably be protected + public void rebuildTableColumns() + { + // Enforce the constraints (QueryAttribute) on the associated columns + for (int c1 = 0; c1 < fullAttributeList.size(); c1++) + initializeTableColumn(c1); + + // Remove all of the columns from the table + for (QueryAttribute aAttribute : fullAttributeList) + myOwner.removeColumn(aAttribute.assocTableColumn); + + // Add in only the columns that are visible + for (QueryAttribute aAttribute : sortedAttributeList) + { + if (aAttribute.isVisible == true) + myOwner.addColumn(aAttribute.assocTableColumn); + } + } + +} diff --git a/src/glum/gui/panel/itemList/BasicItemProcessor.java b/src/glum/gui/panel/itemList/BasicItemProcessor.java new file mode 100644 index 0000000..d93441d --- /dev/null +++ b/src/glum/gui/panel/itemList/BasicItemProcessor.java @@ -0,0 +1,44 @@ +package glum.gui.panel.itemList; + +import java.util.*; + +public abstract class BasicItemProcessor implements ItemProcessor +{ + private Collection myListeners; + + public BasicItemProcessor() + { + myListeners = new ArrayList(); + } + + @Override + public synchronized void addItemChangeListener(ItemChangeListener aListener) + { + myListeners.add(aListener); + } + + @Override + public synchronized void removeItemChangeListener(ItemChangeListener aListener) + { + myListeners.remove(aListener); + } + + /** + * Helper method + */ + protected void notifyListeners() + { + Collection notifySet; + + // Get the listeners + synchronized(this) + { + notifySet = new ArrayList(myListeners); + } + + // Send out the notifications + for (ItemChangeListener aListener : notifySet) + aListener.itemChanged(); + } + +} diff --git a/src/glum/gui/panel/itemList/FilterItemProcessor.java b/src/glum/gui/panel/itemList/FilterItemProcessor.java new file mode 100644 index 0000000..9977431 --- /dev/null +++ b/src/glum/gui/panel/itemList/FilterItemProcessor.java @@ -0,0 +1,92 @@ +package glum.gui.panel.itemList; + +import java.util.ArrayList; +import java.util.Collection; + +import com.google.common.collect.Lists; + +import glum.filter.Filter; +import glum.filter.NullFilter; + +public class FilterItemProcessor extends BasicItemProcessor +{ + // State vars + private ArrayList fullList; + private ArrayList passList; + private Filter activeFilter; + + public FilterItemProcessor() + { + super(); + + fullList = Lists.newArrayList(); + passList = Lists.newArrayList(); + activeFilter = new NullFilter(); + } + + /** + * Returns the current active filter + */ + public Filter getFilter() + { + return activeFilter; + } + + /** + * Sets in the filter used to determine the subset of the items available to this ItemProcessor. + */ + public void setFilter(Filter aFilter) + { + activeFilter = aFilter; + if (activeFilter == null) + activeFilter = new NullFilter(); + + rebuildPassList(); + + // Notify our listeners + notifyListeners(); + } + + /** + * Replaces the current full list of items stored with aItemList. Note that the number of items available by this + * processor may be less than the number of items in aItemList due to the active filter. + */ + public void setItems(Collection aItemList) + { + fullList = new ArrayList(aItemList); + rebuildPassList(); + + // Notify our listeners + notifyListeners(); + } + + @Override + public int getNumItems() + { + return passList.size(); + } + + @Override + public Collection getItems() + { + return Lists.newArrayList(passList); + } + + /** + * Helper method to determine all of the items that are visible by this ItemProcessor. This is accomplished by + * passing them through activeFilter. + */ + private void rebuildPassList() + { + passList = Lists.newArrayList(); + + for (G1 aItem : fullList) + { + if (activeFilter == null || activeFilter.isValid(aItem) == true) + passList.add(aItem); + } + + passList.trimToSize(); + } + +} diff --git a/src/glum/gui/panel/itemList/ItemChangeListener.java b/src/glum/gui/panel/itemList/ItemChangeListener.java new file mode 100644 index 0000000..e8a90a4 --- /dev/null +++ b/src/glum/gui/panel/itemList/ItemChangeListener.java @@ -0,0 +1,10 @@ +package glum.gui.panel.itemList; + +public interface ItemChangeListener +{ + /** + * ItemChangeListener interface methods + */ + public void itemChanged(); + +} diff --git a/src/glum/gui/panel/itemList/ItemHandler.java b/src/glum/gui/panel/itemList/ItemHandler.java new file mode 100644 index 0000000..8588bdc --- /dev/null +++ b/src/glum/gui/panel/itemList/ItemHandler.java @@ -0,0 +1,31 @@ +package glum.gui.panel.itemList; + +import java.util.*; + +import javax.swing.*; + +public interface ItemHandler +{ + /** + * ItemHandler interface methods + */ + public int getColumnCount(); + public Class getColumnClass(int colNum); + public String getColumnLabel(int colNum); + public Collection getColumnLabels(); + public Object getColumnValue(G1 aItem, int colNum); + public void setColumnValue(G1 aItem, int colNum, Object aValue); + public boolean isCellEditable(int colNum); + public boolean isColumnVisible(int colNum); + + /** + * Notifies the ItemHandler of its associated JTable This table should be updated or painted whenever + * the internals of this ItemHandler change + */ + public void initialize(JTable aOwner); + + /** + * Notifies the ItemHandler to synchronize the items to match the state of the associated Columns + */ +// public void synchItems(); +} diff --git a/src/glum/gui/panel/itemList/ItemListPanel.java b/src/glum/gui/panel/itemList/ItemListPanel.java new file mode 100644 index 0000000..2c0df06 --- /dev/null +++ b/src/glum/gui/panel/itemList/ItemListPanel.java @@ -0,0 +1,563 @@ +package glum.gui.panel.itemList; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.table.*; + +import glum.gui.component.GComboBox; +import glum.gui.table.*; + +import com.google.common.collect.*; +import net.miginfocom.swing.MigLayout; + +public class ItemListPanel extends JPanel implements ActionListener, ListSelectionListener, ItemChangeListener +{ + // Gui components + protected JTable myTable; + protected JScrollPane tableScrollPane; + protected ItemListTableModel myTableModel; + protected TableSorter sortTableModel; + protected GComboBox searchBox; + protected JTextField searchTF; + + // State vars + protected ItemHandler myItemHandler; + protected ItemProcessor myItemProcessor; + protected boolean updateNeeded; + + // Communicator vars + protected List myListeners; + + public ItemListPanel(ItemHandler aItemHandler, ItemProcessor aItemProcessor, boolean hasSearchBox, boolean supportsMultipleSelection) + { + // State vars + myItemHandler = aItemHandler; + myItemProcessor = aItemProcessor; + updateNeeded = true; + + // Communicator vars + myListeners = Lists.newLinkedList(); + + // Build the actual GUI + buildGuiArea(hasSearchBox, supportsMultipleSelection); + + // Register for DataChange events and trigger the initial one + myItemProcessor.addItemChangeListener(this); + } + + /** + * addListSelectionListener + */ + public synchronized void addListSelectionListener(ListSelectionListener aListener) + { + myListeners.add(aListener); + } + + /** + * removeListSelectionListener + */ + public synchronized void removeListSelectionListener(ListSelectionListener aListener) + { + myListeners.remove(aListener); + } + + /** + * Method to insert/replace an action for a specific keyboard shortcut + */ + public synchronized void setTableAction(KeyStroke aKeyStroke, Action aAction) + { + myTable.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(aKeyStroke, aAction); + myTable.getActionMap().put(aAction, aAction); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + TableColumn selectedItem; + + // Determine the source + source = e.getSource(); + + if (source == searchTF || source == searchBox) + { + selectedItem = searchBox.getChosenItem(); + selectNextItem(selectedItem, searchTF.getText()); + } + } + + /** + * Returns the object located at the specified (view) row + */ + public synchronized G1 getItem(int aRow) + { + G1 aObj; + + aRow = sortTableModel.modelIndex(aRow); + if (aRow == -1) + return null; + + aObj = myTableModel.getRowItem(aRow); + return aObj; + } + + /** + * Returns the first item that is selected from the table + */ + public synchronized G1 getSelectedItem() + { + G1 selectedObj; + int selectedRow; + + // Ensure the table is up to date + updateTable(); + + selectedRow = myTable.getSelectedRow(); + if (selectedRow == -1) + return null; + + selectedRow = sortTableModel.modelIndex(selectedRow); + if (selectedRow == -1) + return null; + + selectedObj = myTableModel.getRowItem(selectedRow); + return selectedObj; + } + + /** + * getSelectedItems - Returns the list of selected items from the table + */ + public synchronized List getSelectedItems() + { + List aList; + G1 selectedObj; + int[] selectedRows; + int selectedRow; + + // Ensure the table is up to date + updateTable(); + + aList = Lists.newLinkedList(); + selectedRows = myTable.getSelectedRows(); + if (selectedRows != null) + { + for (int aInt : selectedRows) + { + selectedRow = sortTableModel.modelIndex(aInt); + if (selectedRow != -1) + { + selectedObj = myTableModel.getRowItem(selectedRow); + aList.add(selectedObj); + } + } + } + + return aList; + } + + @Override + public void itemChanged() + { + // Mark the table as being outdated + updateNeeded = true; + + // The advantage to the code below (as opposed to SwingUtilities.invokeLater() style) is + // that it is only updated when it absolutely is necessary. Thus if multiple updates come + // in before it is repainted they will be ignored. In the future this method may have an + // argument called isLazy which allow both styles of updating. + repaint(); + } + + /** + * This may be triggered indirectly via a network call after the method repaint() has been called. Do not call this + * method from a non gui thread + */ + @Override + public void paint(Graphics g) + { + // Ensure the table is up to date + updateTable(); + + // Do the actual paint + if (g != null) + super.paint(g); + } + + /** + * Method to set the selected item. This method will first ensure that myItemProcessor and myTable are synchronized + * via the method to updateTable. It is mandatory that this method is executed in the gui swing thread. Note that + * this method will not trigger an event for selection listeners. + */ + public synchronized void selectItem(G1 aObj) + { + int chosenRow; + + // Ensure we are executed only on the proper thread + if (SwingUtilities.isEventDispatchThread() == false) + throw new RuntimeException("ItemListPanel.selectItem() not executed on the AWT event dispatch thread."); + + // Ensure the table is synchronized + updateTable(); + + // Stop listening to events + myTable.getSelectionModel().removeListSelectionListener(this); + + // Select the object + if (aObj == null) + { + myTable.getSelectionModel().clearSelection(); + } + else + { + chosenRow = myTableModel.getRowIndex(aObj); + if (chosenRow != -1) + { + chosenRow = sortTableModel.viewIndex(chosenRow); + myTable.getSelectionModel().addSelectionInterval(chosenRow, chosenRow); + + // Ensure the row is visible + if (JTableScrolling.isRowVisible(myTable, chosenRow) == false) + JTableScrolling.centerRow(myTable, chosenRow); + } + } + + // Resume listening to events + myTable.getSelectionModel().addListSelectionListener(this); + + // Time for a repaint + myTable.repaint(); + } + + /** + * Sets the table bodies color to aColor + */ + public synchronized void setTableBodyColor(Color aColor) + { + tableScrollPane.getViewport().setBackground(aColor); + } + + /** + * Sets whether the table can be sorted + */ + public void setSortingEnabled(boolean aBool) + { + sortTableModel.setSortingEnabled(aBool); + } + + @Override + public void setEnabled(boolean aBool) + { + myTable.setEnabled(aBool); + } + + @Override + public void valueChanged(ListSelectionEvent aEvent) + { + notifyListeners(aEvent); + } + + /** + * Method to provide raw access to the underlying JTable + */ + public JTable getTable() + { + return myTable; + } + + /** + * Forms the actual panel GUI + */ + private void buildGuiArea(boolean hasSearchBox, boolean supportsMultipleSelection) + { + JLabel tmpL; + + if (hasSearchBox == true) + setLayout(new MigLayout("", "0[][][grow]0", "0[grow][]0")); + else + setLayout(new MigLayout("", "0[][][grow]0", "0[grow]0")); + + // Form the table + myTableModel = new ItemListTableModel(myItemHandler); + sortTableModel = new TableSorter(myTableModel); + myTable = new JTable(sortTableModel); + sortTableModel.setTableHeader(myTable.getTableHeader()); +//! sortTableModel.setSortingStatus(0, 1); + + myTable.setBackground(null); + myTable.getSelectionModel().addListSelectionListener(this); + if (supportsMultipleSelection == false) + myTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + else + myTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + // Notify the ItemHandler of its associated table and initialize the table + myItemHandler.initialize(myTable); + + // Create the scroll pane and add the table to it. + tableScrollPane = new JScrollPane(myTable); + add(tableScrollPane, "growx,growy,span 3"); + + // The search section + searchBox = null; + searchTF = null; + if (hasSearchBox == true) + { + tmpL = new JLabel("Find:"); + add(tmpL, "newline,span 1"); + + searchBox = new GComboBox(this, new SearchBoxRenderer()); + searchBox.setMaximumSize(searchBox.getPreferredSize()); + add(searchBox, "span 1"); + + searchTF = new JTextField(""); + searchTF.addActionListener(this); + add(searchTF, "growx,span 1"); + + // Set in the preferred font + tmpL.setFont(searchTF.getFont()); + searchBox.setFont(searchTF.getFont()); + } + + // Set the default preferred size of the ListPanel + setPreferredSize(new Dimension(myTable.getPreferredSize().width, 150)); + } + + /** + * notifyListeners + */ + protected void notifyListeners(ListSelectionEvent aEvent) + { + List tmpList; + ListSelectionEvent tmpEvent; + + synchronized(this) + { + tmpList = Lists.newArrayList(myListeners); + } + + // Notify our listeners + tmpEvent = new ListSelectionEvent(this, aEvent.getFirstIndex(), aEvent.getLastIndex(), aEvent.getValueIsAdjusting()); + for (ListSelectionListener aListener : tmpList) + aListener.valueChanged(tmpEvent); + } + + /** + * rebuildItemList + */ + protected synchronized void rebuildItemList() + { + Collection itemList; + Collection selectedObjSet; + int[] selectedRows; + int aRow; + + // Insanity check + if (myItemProcessor == null) + return; + + // Get the old selected items + selectedObjSet = new LinkedList(); + selectedRows = myTable.getSelectedRows(); + if (selectedRows != null) + { + for (int aInt : selectedRows) + { + aRow = sortTableModel.modelIndex(aInt); + selectedObjSet.add(myTableModel.getRowItem(aRow)); + } + } + + // Suspend listening to selection change events + myTable.getSelectionModel().removeListSelectionListener(this); + + // Update our table with the new set of items + itemList = myItemProcessor.getItems(); + myTableModel.clear(); + myTableModel.addItems(itemList); + + // Reselect the old selected items + myTable.getSelectionModel().clearSelection(); + for (G1 aObj : selectedObjSet) + { + aRow = myTableModel.getRowIndex(aObj); + if (aRow != -1) + { + aRow = sortTableModel.viewIndex(aRow); + myTable.getSelectionModel().addSelectionInterval(aRow, aRow); + } + } + + // Restore listening to selection change events + myTable.getSelectionModel().addListSelectionListener(this); + } + + /** + * Rebuild the searchBox so that it contains all of the table columns. + */ + protected synchronized void rebuildSearchBox() + { + TableColumn selectedItem; + int numCols; + + // Insanity check + if (searchBox == null) + return; + + // Save off the currently selected object + selectedItem = searchBox.getChosenItem(); + + // Reconstitude searchBox + numCols = myTable.getColumnCount(); + searchBox.removeAllItems(); + for (int c1 = 0; c1 < numCols; c1++) + searchBox.addItem(myTable.getTableHeader().getColumnModel().getColumn(c1)); + + // Set up the searchBox to appropriate state + searchBox.removeActionListener(this); + if (selectedItem != null) + searchBox.setSelectedItem(selectedItem); + searchBox.addActionListener(this); + + searchBox.setMaximumSize(searchBox.getPreferredSize()); + } + + /** + * Utility to locate the next item to be selected, and change the section to that such item + */ + protected synchronized void selectNextItem(TableColumn aTableColumn, String searchStr) + { + TableCellRenderer aRenderer; + JLabel tmpL; + Object aObj; + String currStr; + int colNum, chosenRow, startRow, numRows; + int cX; + boolean hardMatchRequired; + + if (aTableColumn == null || searchStr == null) + return; + + // Retrieve the model index and table renderer + aRenderer = aTableColumn.getCellRenderer(); + colNum = aTableColumn.getModelIndex(); + + // Is a hard match required + hardMatchRequired = false; + if (searchStr.endsWith(" ") == true) + { + hardMatchRequired = true; + searchStr = searchStr.substring(0, searchStr.length() - 1); + } + + startRow = myTable.getSelectedRow(); + if (startRow == -1) + startRow = 0; + else + startRow++; + + numRows = sortTableModel.getRowCount(); + if (numRows == 0) + return; + + // Search lower half of table + chosenRow = -1; + for (cX = startRow; cX < numRows; cX++) + { + aObj = sortTableModel.getValueAt(cX, colNum); + if (aObj != null) + { + + tmpL = (JLabel)aRenderer.getTableCellRendererComponent(myTable, aObj, false, false, cX, colNum); + currStr = tmpL.getText(); + if (currStr != null) + { + if (hardMatchRequired == true) + { + if (currStr.equals(searchStr) == true) + { + chosenRow = cX; + break; + } + } + else if (currStr.startsWith(searchStr) == true) + { + chosenRow = cX; + break; + } + } + } + } + + // Search upper half of table + if (chosenRow == -1) + { + for (cX = 0; cX < startRow; cX++) + { + aObj = sortTableModel.getValueAt(cX, colNum); + if (aObj != null) + { + tmpL = (JLabel)aRenderer.getTableCellRendererComponent(myTable, aObj, false, false, cX, colNum); + currStr = tmpL.getText(); + if (currStr != null) + { + if (hardMatchRequired == true) + { + if (currStr.equals(searchStr) == true) + { + chosenRow = cX; + break; + } + } + else if (currStr.startsWith(searchStr) == true) + { + chosenRow = cX; + break; + } + } + } + } + } + + if (chosenRow != -1) + { + myTable.getSelectionModel().setSelectionInterval(chosenRow, chosenRow); + + // Ensure the row is visible + if (JTableScrolling.isRowVisible(myTable, chosenRow) == false) + { + JTableScrolling.centerRow(myTable, chosenRow); + } + } + } + + /** + * Utility method to execute the actual synchronization of the JTable with myItemProcessor. Note that this will + * coalesce multiple update requests into one (via the var updateNeeded). + */ + protected synchronized void updateTable() + { + // Bail if an update is no longer needed. + // If an update was originally scheduled, this will be false + // due to coalesion. + if (updateNeeded == false) + return; +//System.out.println("ItemListPanel.updateTable() Addr:" + this.hashCode()); + +// // Ensure we are executed only on the proper thread +// if (SwingUtilities.isEventDispatchThread() == false) +// throw new RuntimeException("ItemListPanel.updateTable() not executed on the AWT event dispatch thread."); + + // Perform the actual synchronization + rebuildItemList(); + rebuildSearchBox(); + + // Mark any future (already scheduled) requests as filled. + updateNeeded = false; + } + +} diff --git a/src/glum/gui/panel/itemList/ItemListTableModel.java b/src/glum/gui/panel/itemList/ItemListTableModel.java new file mode 100644 index 0000000..46a9aef --- /dev/null +++ b/src/glum/gui/panel/itemList/ItemListTableModel.java @@ -0,0 +1,148 @@ +package glum.gui.panel.itemList; + +import java.util.*; +import javax.swing.table.AbstractTableModel; + +public class ItemListTableModel extends AbstractTableModel +{ + private ItemHandler myHandler; + private ArrayList myVector; + + /** + * Constructor + */ + public ItemListTableModel(ItemHandler aHandler) + { + myHandler = aHandler; + + myVector = new ArrayList(); + } + + @Override + public int getColumnCount() + { + if (myHandler == null) + return 0; + + return myHandler.getColumnCount(); + } + + @Override + public int getRowCount() + { + return myVector.size(); + } + + @Override + public String getColumnName(int col) + { + if (myHandler == null) + return null; + + return myHandler.getColumnLabel(col); + } + + @Override + public Object getValueAt(int row, int col) + { + if (myHandler == null) + return null; + + return myHandler.getColumnValue(myVector.get(row), col); + } + + @Override + public Class getColumnClass(int col) + { + return myHandler.getColumnClass(col); + } + + @Override + public boolean isCellEditable(int row, int col) + { + if (myHandler == null) + return false; + + return myHandler.isCellEditable(col); + } + + @Override + public void setValueAt(Object value, int row, int col) + { + if (myHandler == null) + return; + + myHandler.setColumnValue(myVector.get(row), col, value); + } + + /** + * Removes all values from the TableModel + */ + public void clear() + { + int endIndex; + + if (myVector.isEmpty() == true) + return; + + endIndex = myVector.size() - 1; + myVector.clear(); + + fireTableRowsDeleted(0, endIndex); + } + + /** + * Returns the row index associated with aItem + */ + public int getRowIndex(G1 aItem) + { + int aIndex; + + if (aItem == null) + return -1; + + aIndex = 0; + for (G1 aObj : myVector) + { + if (aObj.equals(aItem) == true) + return aIndex; + + aIndex++; + } + + return -1; + } + + /** + * Returns the item associated with the row + */ + public G1 getRowItem(int row) + { + if (row < 0 || row >= myVector.size()) + return null; + + return myVector.get(row); + } + + /** + * Adds the collection to our TableModel + */ + public void addItems(Collection aCollection) + { + int startIndex, endIndex; + + if (aCollection == null) + return; + + if (aCollection.isEmpty() == true) + return; + + startIndex = myVector.size(); + endIndex = startIndex + aCollection.size(); + + myVector.addAll(aCollection); + + fireTableRowsInserted(startIndex, endIndex); + } + +} diff --git a/src/glum/gui/panel/itemList/ItemProcessor.java b/src/glum/gui/panel/itemList/ItemProcessor.java new file mode 100644 index 0000000..6877771 --- /dev/null +++ b/src/glum/gui/panel/itemList/ItemProcessor.java @@ -0,0 +1,26 @@ +package glum.gui.panel.itemList; + +import java.util.*; + +public interface ItemProcessor +{ + /** + * Returns the number of items in this ItemProcessor + */ + public int getNumItems(); + + /** + * Returns a list of all items in this ItemProcessor + */ + public Collection getItems(); + + /** + * Registers for notification when items are added/removed from this ItemProcessor. + */ + public void addItemChangeListener(ItemChangeListener aItemChangeListener); + + /** + * Deregisters for notification of item list change events. + */ + public void removeItemChangeListener(ItemChangeListener aItemChangeListener); +} diff --git a/src/glum/gui/panel/itemList/RegistryProcessor.java b/src/glum/gui/panel/itemList/RegistryProcessor.java new file mode 100644 index 0000000..673c9a1 --- /dev/null +++ b/src/glum/gui/panel/itemList/RegistryProcessor.java @@ -0,0 +1,55 @@ +package glum.gui.panel.itemList; + +import java.util.*; + +import glum.registry.*; + +public class RegistryProcessor extends BasicItemProcessor implements ResourceListener +{ + private Registry refRegistry; + private Class resourceClass; + private Object resourceKey; + + private Collection itemList; + + /** + * Constructor + */ + public RegistryProcessor(Registry aRegistry, Object aResourceKey, Class aResourceClass) + { + super(); + + refRegistry = aRegistry; + resourceClass = aResourceClass; + resourceKey = aResourceKey; + + // Initialize our state vars + itemList = new ArrayList(); + + // Register for events of interest + refRegistry.addResourceListener(resourceKey, this); + } + + @Override + public synchronized Collection getItems() + { + return new ArrayList(itemList); + } + + @Override + public synchronized int getNumItems() + { + return itemList.size(); + } + + @Override + public void resourceChanged(Registry aRegistry, Object aKey) + { + // Retrieve the list of tiles + itemList = refRegistry.getResourceItems(resourceKey, resourceClass); + + // Notify our listeners + notifyListeners(); + } + +} diff --git a/src/glum/gui/panel/itemList/SearchBoxRenderer.java b/src/glum/gui/panel/itemList/SearchBoxRenderer.java new file mode 100644 index 0000000..944903b --- /dev/null +++ b/src/glum/gui/panel/itemList/SearchBoxRenderer.java @@ -0,0 +1,36 @@ +package glum.gui.panel.itemList; + +import java.awt.*; +import javax.swing.*; +import javax.swing.table.*; + +public class SearchBoxRenderer extends DefaultListCellRenderer +{ + /** + * Constructor + */ + public SearchBoxRenderer() + { + super(); + } + + @Override + public Component getListCellRendererComponent(JList list, Object aObj, int index, boolean isSelected, boolean hasFocus) + { + JLabel retL; + String aStr; + + retL = (JLabel)super.getListCellRendererComponent(list, aObj, index, isSelected, hasFocus); + if (aObj instanceof TableColumn) + { + aStr = "" + ((TableColumn)aObj).getHeaderValue(); + if (aStr.equals("null") == true || aStr.equals("") == true) + aStr = "" + ((TableColumn)aObj).getIdentifier(); + + retL.setText(aStr); + } + + return retL; + } + +} diff --git a/src/glum/gui/panel/itemList/StaticItemProcessor.java b/src/glum/gui/panel/itemList/StaticItemProcessor.java new file mode 100644 index 0000000..141025c --- /dev/null +++ b/src/glum/gui/panel/itemList/StaticItemProcessor.java @@ -0,0 +1,56 @@ +package glum.gui.panel.itemList; + +import java.util.*; + +public class StaticItemProcessor extends BasicItemProcessor +{ + private ArrayList itemList; + + public StaticItemProcessor() + { + super(); + + itemList = new ArrayList(); + } + + public StaticItemProcessor(List aList) + { + super(); + + itemList = new ArrayList(aList); + } + + /** + * Return the index of the specified item + * + * @see ArrayList#indexOf + */ + public int indexOf(G1 aItem) + { + return itemList.indexOf(aItem); + } + + /** + * Replaces the static list of items stored with aItemList + */ + public void setItems(Collection aItemList) + { + itemList = new ArrayList(aItemList); + + // Notify our listeners + notifyListeners(); + } + + @Override + public synchronized ArrayList getItems() + { + return new ArrayList(itemList); + } + + @Override + public synchronized int getNumItems() + { + return itemList.size(); + } + +} diff --git a/src/glum/gui/panel/itemList/config/AddProfilePanel.java b/src/glum/gui/panel/itemList/config/AddProfilePanel.java new file mode 100644 index 0000000..3063e86 --- /dev/null +++ b/src/glum/gui/panel/itemList/config/AddProfilePanel.java @@ -0,0 +1,73 @@ +package glum.gui.panel.itemList.config; + +import glum.gui.panel.generic.TextInputPanel; + +import java.awt.*; +import java.util.*; +import com.google.common.collect.Sets; + +public class AddProfilePanel extends TextInputPanel +{ + // Constants + public static final String DEFAULT_NAME = "Default"; + + // State vars + protected Set reservedSet; + + public AddProfilePanel(Component aParent) + { + super(aParent); + + // Set in a more specific title and input label + titleL.setText("Save Profile As"); + inputL.setText("Name:"); + + reservedSet = Sets.newHashSet(); + } + + /** + * Sets in all of the names currently used. + */ + public void setReservedNames(Collection aReservedSet) + { + reservedSet.clear(); + reservedSet.addAll(aReservedSet); + } + + @Override + protected void updateGui() + { + String inputStr, infoMsg; + boolean isEnabled; + + // Assume the GUI is invalid + isEnabled = false; + + // Retrieve the name + inputStr = inputTF.getText(); + + // Determine the validity of specified name + if (inputStr.equals("") == true) + { + infoMsg = "Please enter a valid profile name."; + } + else if (inputStr.equals(DEFAULT_NAME) == true) + { + infoMsg = "Name is reserved. Please pick another."; + } + else if (reservedSet.contains(inputStr) == true) + { + infoMsg = "Name in use. Profile will be overwritten."; + isEnabled = true; + } + else + { + infoMsg = ""; + isEnabled = true; + } + + infoL.setText(infoMsg); + acceptB.setEnabled(isEnabled); + } + +} diff --git a/src/glum/gui/panel/itemList/config/ConfigHandler.java b/src/glum/gui/panel/itemList/config/ConfigHandler.java new file mode 100644 index 0000000..2521c06 --- /dev/null +++ b/src/glum/gui/panel/itemList/config/ConfigHandler.java @@ -0,0 +1,77 @@ +package glum.gui.panel.itemList.config; + +import glum.gui.panel.itemList.BasicItemHandler; +import glum.gui.panel.itemList.query.QueryAttribute; +import glum.gui.panel.itemList.query.QueryComposer; + +public class ConfigHandler extends BasicItemHandler +{ + public ConfigHandler(QueryComposer aComposer) + { + super(aComposer); + } + + @Override + public Object getColumnValue(QueryAttribute aObj, int colNum) + { + Enum refKey; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return null; + + refKey = fullAttributeList.get(colNum).refKey; + return getColumnValue(aObj, refKey); + } + + @Override + public void setColumnValue(QueryAttribute aObj, int colNum, Object aValue) + { + Enum refKey; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + + refKey = fullAttributeList.get(colNum).refKey; + setColumnValue(aObj, refKey, aValue); + } + + /** + * Method to get the value from aObj described by aRefKey + */ + public Object getColumnValue(QueryAttribute aItem, Enum aRefKey) + { + switch ((ConfigLookUp)aRefKey) + { + case IsVisible: + return aItem.isVisible; + + case Name: + return aItem.refKey; + + case Label: + return aItem.label; + + default: + break; + } + + return null; + } + + /** + * Method to get the value from aObj described by aRefKey + */ + public void setColumnValue(QueryAttribute aItem, Enum aRefKey, Object aValue) + { + if (aRefKey == ConfigLookUp.IsVisible) + { + aItem.isVisible = (Boolean)aValue; + return; + } + + throw new RuntimeException("Unsupported Operation."); + } + +} diff --git a/src/glum/gui/panel/itemList/config/ConfigLookUp.java b/src/glum/gui/panel/itemList/config/ConfigLookUp.java new file mode 100644 index 0000000..05ffd72 --- /dev/null +++ b/src/glum/gui/panel/itemList/config/ConfigLookUp.java @@ -0,0 +1,9 @@ +package glum.gui.panel.itemList.config; + +public enum ConfigLookUp +{ + IsVisible, + Name, + Label, + +} diff --git a/src/glum/gui/panel/itemList/config/EditTablePanel.java b/src/glum/gui/panel/itemList/config/EditTablePanel.java new file mode 100644 index 0000000..e8aa8a2 --- /dev/null +++ b/src/glum/gui/panel/itemList/config/EditTablePanel.java @@ -0,0 +1,437 @@ +package glum.gui.panel.itemList.config; + +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.util.*; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; + +import com.google.common.collect.Sets; + +import glum.gui.*; +import glum.gui.action.*; +import glum.gui.component.GComboBox; +import glum.gui.component.GTextField; +import glum.gui.icon.ArrowNorthIcon; +import glum.gui.icon.ArrowSouthIcon; +import glum.gui.icon.DeleteIcon; +import glum.gui.misc.*; +import glum.gui.panel.*; +import glum.gui.panel.itemList.BasicItemHandler; +import glum.gui.panel.itemList.ItemHandler; +import glum.gui.panel.itemList.ItemListPanel; +import glum.gui.panel.itemList.StaticItemProcessor; +import glum.gui.panel.itemList.query.*; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import net.miginfocom.swing.MigLayout; + +public class EditTablePanel extends GlassPanel implements ActionListener, ZioRaw, ListSelectionListener +{ + // GUI vars + protected JLabel titleL; + protected JRadioButton profileRB, customRB; + protected GComboBox profileBox; + protected ItemListPanel listPanel; + protected BooleanCellEditor col0Editor; + protected BooleanCellRenderer col0Renderer; + protected DefaultTableCellRenderer col1Renderer; + protected JButton closeB, saveB, upB, downB, deleteB; + protected JLabel labelL; + protected GTextField labelTF; + protected Font smallFont; + protected AddProfilePanel profilePanel; + + // State vars + protected BasicItemHandler refItemHandler; + protected StaticItemProcessor myItemProcessor; + + public EditTablePanel(Component aParent, BasicItemHandler aItemHandler) + { + super(aParent); + + // State vars + refItemHandler = aItemHandler; + + // Build the actual GUI + smallFont = (new JTextField()).getFont(); + buildGuiArea(); + setPreferredSize(new Dimension(250, getPreferredSize().height)); + + profilePanel = new AddProfilePanel(this); + profilePanel.setSize(375, 140); + profilePanel.addActionListener(this); + + syncGui(); + updateGui(); + + // Set up some keyboard shortcuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(closeB)); + } + + /** + * Adds a predefined profile (as specified in aConfig) to the + * list of available profiles. + * @param aConfig + */ + public void addConfig(ProfileConfig aConfig) + { + profileBox.addItem(aConfig); + } + + public ArrayList getAllConfig() + { + return profileBox.getAllItems(); + } + + public BasicItemHandler getItemHandler() + { + return refItemHandler; + } + + /** + * Synchronizes the gui to match the model + */ + public void syncGui() + { + myItemProcessor.setItems(refItemHandler.getSortedAttributes()); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + QueryAttribute aItem; + int index; + + aItem = listPanel.getSelectedItem(); + + source = aEvent.getSource(); + if (source == labelTF) + { + updateLayerAttribute(); + return; + } + else if (source == deleteB) + { + profileBox.removeItem(profileBox.getChosenItem()); + actionPerformed(new ActionEvent(profileBox, ID_UPDATE, null)); + } + else if (source == closeB) + { + setVisible(false); + notifyListeners(this, 0, "Close"); + } + else if (source == saveB) + { + Set nameSet; + + nameSet = Sets.newHashSet(); + for (ProfileConfig aProfile : profileBox.getAllItems()) + nameSet.add(aProfile.getName()); + + profilePanel.resetGui(); + profilePanel.setReservedNames(nameSet); + profilePanel.setVisible(true); + } + else if (source == upB) + { + index = myItemProcessor.indexOf(aItem); + refItemHandler.moveSortedAttribute(index, index - 1); + refItemHandler.rebuildTableColumns(); + + myItemProcessor.setItems(refItemHandler.getSortedAttributes()); + } + else if (source == downB) + { + index = myItemProcessor.indexOf(aItem); + refItemHandler.moveSortedAttribute(index, index + 1); + refItemHandler.rebuildTableColumns(); + + myItemProcessor.setItems(refItemHandler.getSortedAttributes()); + } + else if (source == col0Editor) + { + refItemHandler.rebuildTableColumns(); + } + else if (source == profileBox || source == profileRB) + { + ProfileConfig aConfig; + + aConfig = profileBox.getChosenItem(); + refItemHandler.setOrderAndConfig(aConfig.getItems()); + refItemHandler.rebuildTableColumns(); + + myItemProcessor.setItems(refItemHandler.getSortedAttributes()); + } + else if (source == profilePanel && aEvent.getID() == AddProfilePanel.ID_ACCEPT) + { + ProfileConfig aProfile; + Collection aItemList; + String aName; + + aName = profilePanel.getInput(); + aItemList = myItemProcessor.getItems(); + for (QueryAttribute aAttribute : aItemList) + aAttribute.synchronizeAttribute(); + + aProfile = new ProfileConfig(aName, aItemList); + profileBox.addItem(aProfile); + } + + updateGui(); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + ProfileConfig aProfile; + int numItems, profileIndex; + boolean aBool; + + super.zioReadRaw(aStream); + + aStream.readVersion(0); + + aBool = aStream.readBool(); + customRB.setSelected(aBool); + profileRB.setSelected(!aBool); + + numItems = aStream.readInt(); + profileBox.removeAllItems(); + for (int c1 = 0; c1 < numItems; c1++) + { + aProfile = new ProfileConfig("unnamed", myItemProcessor.getItems()); + aProfile.zioReadRaw(aStream); + + profileBox.addItem(aProfile); + } + + profileIndex = aStream.readInt(); + profileBox.removeActionListener(this); + if (profileIndex >= 0) + profileBox.setSelectedIndex(profileIndex); + profileBox.addActionListener(this); + + refItemHandler.zioReadRaw(aStream); + + updateGui(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + int numItems, profileIndex; + boolean aBool; + + super.zioWriteRaw(aStream); + + aStream.writeVersion(0); + + aBool = customRB.isSelected(); + aStream.writeBool(aBool); + + numItems = profileBox.getAllItems().size(); + aStream.writeInt(numItems); + + for (ProfileConfig aProfile : profileBox.getAllItems()) + { + aProfile.zioWriteRaw(aStream); + } + + profileIndex = profileBox.getSelectedIndex(); + aStream.writeInt(profileIndex); + + refItemHandler.zioWriteRaw(aStream); + } + + @Override + public void valueChanged(ListSelectionEvent e) + { + // Update only after the user has released the mouse + if (e.getValueIsAdjusting() == true) + return; + + updateGui(); + } + + /** + * Builds the main GUI area + */ + protected void buildGuiArea() + { + JPanel tmpPanel; + ProfileConfig aConfig; + + // Form the layout + setLayout(new MigLayout("", "[left][grow][]", "[][][]3[grow][]")); + + // Title Area + titleL = new JLabel("Table Config", JLabel.CENTER); + add(titleL, "growx,span 2,wrap"); + + // Profile Area + profileRB = GuiUtil.createJRadioButton("Profile:", this, smallFont); + add(profileRB, "span 1"); + + profileBox = new GComboBox(); + profileBox.addActionListener(this); + profileBox.setFont(smallFont); + add(profileBox, "growx,span 1"); + + deleteB = GuiUtil.createJButton(new DeleteIcon(14), this); + add(deleteB, "align right,span 1,w 20!, h 20!,wrap"); + + // Custom Area + customRB = GuiUtil.createJRadioButton("Custom:", this, smallFont); + customRB.setSelected(true); + add(customRB, "span 1"); + + //saveB = GuiUtil.createJButton(new ArrowNorthIcon(14), this); + //add(saveB, "align right,span 1,w 18!, h 18!"); + saveB = GuiUtil.createJButton("Save", this, smallFont); + add(saveB, "align right,span 2,wrap"); + + tmpPanel = buildItemListTablePanel(); + tmpPanel.setBorder(new EmptyBorder(0, 15, 0, 0)); + add(tmpPanel, "growx,growy,span,wrap"); + + // Link the radio buttons + GuiUtil.linkRadioButtons(profileRB, customRB); + + // Build the config area + tmpPanel = buildConfigPanel(); + add(tmpPanel, "growx,span,wrap"); + + // Action area + closeB = GuiUtil.createJButton("Close", this, smallFont); + add(closeB, "align right,span"); + + // Add in the default profile + aConfig = new ProfileConfig(AddProfilePanel.DEFAULT_NAME, refItemHandler.getSortedAttributes()); + profileBox.addItem(aConfig); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + /** + * Utility method to build the configuration area for the individual attributes + */ + protected JPanel buildConfigPanel() + { + JPanel tmpPanel; + + // Form the layout + tmpPanel = new JPanel(); + tmpPanel.setLayout(new MigLayout("", "0[grow][][]0", "0[][]10")); + + // Title Area + labelL = GuiUtil.createJLabel("Label:", smallFont); + tmpPanel.add(labelL, "growx,span 1"); + + upB = GuiUtil.createJButton(new ArrowNorthIcon(14), this); + tmpPanel.add(upB, "align right,span 1,w 18!, h 18!"); + + downB = GuiUtil.createJButton(new ArrowSouthIcon(14), this); + tmpPanel.add(downB, "align right,span 1,w 18!, h 18!,wrap"); + + labelTF = new GTextField(this); + tmpPanel.add(labelTF, "growx,span 3,wrap"); + + return tmpPanel; + } + + /** + * Utility method to build the query item list table + */ + protected JPanel buildItemListTablePanel() + { + QueryComposer aComposer; + ItemHandler aItemHandler; + + aComposer = new QueryComposer(); + aComposer.addAttribute(ConfigLookUp.IsVisible, Boolean.class, "", ""); + aComposer.addAttribute(ConfigLookUp.Name, String.class, "Name", null); + aComposer.addAttribute(ConfigLookUp.Label, String.class, "Label", null); + + col0Editor = new BooleanCellEditor(); + col0Editor.addActionListener(this); + col0Renderer = new BooleanCellRenderer(); + col1Renderer = new DefaultTableCellRenderer(); + aComposer.setEditor(ConfigLookUp.IsVisible, col0Editor); + aComposer.setRenderer(ConfigLookUp.IsVisible, col0Renderer); + aComposer.setRenderer(ConfigLookUp.Name, col1Renderer); + aComposer.setRenderer(ConfigLookUp.Label, col1Renderer); + + aItemHandler = new ConfigHandler(aComposer); + + myItemProcessor = new StaticItemProcessor(); + myItemProcessor.setItems(refItemHandler.getSortedAttributes()); + + listPanel = new ItemListPanel(aItemHandler, myItemProcessor, false, false); + listPanel.setSortingEnabled(false); + listPanel.addListSelectionListener(this); + return listPanel; + } + + /** + * Synchronizes the model to match the label gui + */ + protected void updateLayerAttribute() + { + QueryAttribute chosenItem; + + chosenItem = listPanel.getSelectedItem(); + chosenItem.label = labelTF.getText(); + + chosenItem.assocTableColumn.setHeaderValue(chosenItem.label); + + listPanel.repaint(); +//getParent().repaint(); + } + + /** + * Synchronizes our GUI vars + */ + protected void updateGui() + { + QueryAttribute chosenItem; + boolean isEnabled; + int chosenIndex; + + // Update the profile area + isEnabled = profileRB.isSelected(); + profileBox.setEnabled(isEnabled); + + isEnabled = isEnabled & (profileBox.getAllItems().size() > 1); + deleteB.setEnabled(isEnabled); + + // Update the custom area + isEnabled = customRB.isSelected(); + saveB.setEnabled(isEnabled); + GuiUtil.setEnabled(listPanel, isEnabled); + col0Renderer.setEnabled(isEnabled); + col1Renderer.setEnabled(isEnabled); + + chosenItem = listPanel.getSelectedItem(); + chosenIndex = myItemProcessor.indexOf(chosenItem); + if (chosenItem != null) + labelTF.setText(chosenItem.label); + + isEnabled = isEnabled & (chosenItem != null); + labelL.setEnabled(isEnabled); + labelTF.setEnabled(isEnabled); + + upB.setEnabled(isEnabled & (chosenIndex > 0)); + downB.setEnabled(isEnabled & (chosenIndex+1 < myItemProcessor.getNumItems())); + + listPanel.repaint(); + } + +} diff --git a/src/glum/gui/panel/itemList/config/EditTablePanelClassic.java b/src/glum/gui/panel/itemList/config/EditTablePanelClassic.java new file mode 100644 index 0000000..5cacc6f --- /dev/null +++ b/src/glum/gui/panel/itemList/config/EditTablePanelClassic.java @@ -0,0 +1,180 @@ +package glum.gui.panel.itemList.config; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.border.*; + +import glum.gui.*; +import glum.gui.action.*; +import glum.gui.component.GComboBox; +import glum.gui.panel.*; +import glum.gui.panel.itemList.query.*; + +import net.miginfocom.swing.MigLayout; + +public class EditTablePanelClassic extends GlassPanel implements ActionListener +{ + // GUI vars + protected JLabel titleL; + protected JRadioButton profileRB, customRB; + protected GComboBox profileBox; + protected JButton closeB; + + // State vars + protected QueryItemHandler refItemHandler; + protected Map actionMap; + + public EditTablePanelClassic(Component aParent, QueryItemHandler aItemHandler) + { + super(aParent); + + // State vars + refItemHandler = aItemHandler; + actionMap = new HashMap(); + + // Build the actual GUI + buildGuiArea(); + setPreferredSize(new Dimension(200, getPreferredSize().height)); + updateGui(); + + // Set up some keyboard shortcuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(closeB)); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + + source = e.getSource(); + + if (source == closeB) + { + setVisible(false); + notifyListeners(this, 0, "Close"); + } + else if (source == profileBox || source == profileRB) + { + ProfileConfig aConfig; + QueryAttribute aAttribute; + + aConfig = profileBox.getChosenItem(); + refItemHandler.setOrderAndConfig(aConfig.getItems()); + refItemHandler.rebuildTableColumns(); + + for (JCheckBox itemCB : actionMap.keySet()) + { + aAttribute = actionMap.get(itemCB); + itemCB.setSelected(aAttribute.isVisible); + } + } + else if (source instanceof JCheckBox) + { + QueryAttribute aAttribute; + JCheckBox tmpCB; + + tmpCB = (JCheckBox)source; + aAttribute = actionMap.get(tmpCB); + aAttribute.isVisible = tmpCB.isSelected(); + + refItemHandler.rebuildTableColumns(); + } + + updateGui(); + } + + public void addConfig(ProfileConfig aConfig) + { + profileBox.addItem(aConfig); + } + + /** + * Builds the main GUI area + */ + protected void buildGuiArea() + { + JPanel tmpPanel; + Border border1, border2; + + // Form the grid bag constraints + setLayout(new MigLayout("", "[left][grow]", "[]")); + + // Title Area + titleL = new JLabel("Table Config", JLabel.CENTER); + add(titleL, "growx,span 2,wrap"); + + // Profile Area + profileRB = new JRadioButton("Profile:"); + profileRB.addActionListener(this); + add(profileRB, "span 1"); + + profileBox = new GComboBox(); + profileBox.addActionListener(this); + add(profileBox, "growx,span 1,wrap"); + + // Custom Area + customRB = new JRadioButton("Custom:", true); + customRB.addActionListener(this); + add(customRB, "span 1,wrap"); + + tmpPanel = buildQueryItemPanel(); + border1 = new EmptyBorder(0, 10, 0, 10); + border2 = new BevelBorder(BevelBorder.RAISED); + tmpPanel.setBorder(new CompoundBorder(border2, border1)); + tmpPanel.setEnabled(false); + add(tmpPanel, "growx,span 2,wrap"); + + // Link the radio buttons + GuiUtil.linkRadioButtons(profileRB, customRB); + + // Build the default profile box + ProfileConfig aConfig; + aConfig = new ProfileConfig(AddProfilePanel.DEFAULT_NAME, refItemHandler.getSortedAttributes()); + addConfig(aConfig); + + // Action area + closeB = GuiUtil.createJButton("Close", this); + add(closeB, "align right,span 2"); + + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + protected JPanel buildQueryItemPanel() + { + Collection attrList; + JPanel aPanel; + JCheckBox tmpCB; + + aPanel = new JPanel(); + aPanel.setLayout(new BoxLayout(aPanel, BoxLayout.Y_AXIS)); + + attrList = refItemHandler.getSortedAttributes(); + for (QueryAttribute aAttr : attrList) + { + tmpCB = new JCheckBox(aAttr.label, aAttr.isVisible); + tmpCB.addActionListener(this); + aPanel.add(tmpCB); + + actionMap.put(tmpCB, aAttr); + } + + return aPanel; + } + + protected void updateGui() + { + boolean isEnabled; + + // Update the profile area + isEnabled = profileRB.isSelected(); + profileBox.setEnabled(isEnabled); + + // Update the custom area + isEnabled = customRB.isSelected(); + for (JCheckBox aItemCB : actionMap.keySet()) + aItemCB.setEnabled(isEnabled); + } + +} diff --git a/src/glum/gui/panel/itemList/config/ProfileConfig.java b/src/glum/gui/panel/itemList/config/ProfileConfig.java new file mode 100644 index 0000000..4128473 --- /dev/null +++ b/src/glum/gui/panel/itemList/config/ProfileConfig.java @@ -0,0 +1,152 @@ +package glum.gui.panel.itemList.config; + +import glum.gui.panel.itemList.query.QueryAttribute; +import glum.gui.panel.itemList.query.QueryComposer; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +import java.io.IOException; +import java.util.*; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * + * Class that stores a list of QuerryAttributes used to describe a specific configuration + * of ItemListPanel and associated table. + * + */ +public class ProfileConfig implements ZioRaw +{ + // State vars + protected String myName; + protected Map itemMap; + + /** + * Constructor. Form a deep copy of the list of QueryAttributes. This is to + * ensure that this profile will not share QueryAttributes with other profiles + * leading to inadvertent tampering. + * + * @param aName: Name of the profile + * @param aItemSet: Set of all QuerryAttributes associated with this profile + */ + public ProfileConfig(String aName, Collection aItemList) + { + myName = aName; + + itemMap = Maps.newLinkedHashMap(); + for (QueryAttribute aItem : aItemList) + itemMap.put(aItem.modelIndex, new QueryAttribute(aItem)); + } + + /** + * Constructor. All of the QueryAttribute will be turned off by default. + * It is imperative that you call setVisibleItems() to allow this profile + * to display columns when activated. + * + * @param aName: Name of the profile + * @param aComposer: The composer which provides all QuerryAttributes + * associated with this profile + */ + public ProfileConfig(String aName, QueryComposer aComposer) + { + this(aName, aComposer.getItems()); + + for (QueryAttribute aItem : itemMap.values()) + aItem.isVisible = false; + } + + /** + * Sets the specified QueryAttributes as visible for this ProfileConfig + */ + public void setVisibleItems(Collection aItemList) + { + for (QueryAttribute aItem : aItemList) + itemMap.get(aItem.modelIndex).isVisible = true; + } + + /** + * Sets the specified QueryAttributes as visible for this ProfileConfig + */ + public void setVisibleItems(QueryAttribute... aItemList) + { + for (QueryAttribute aItem : aItemList) + itemMap.get(aItem.modelIndex).isVisible = true; + } + + /** + * Returns the (display) name of this ProfileConfig + */ + public String getName() + { + return myName; + } + + /** + * Returns the QueryAttributes associated with this ProfileConfig + */ + public ArrayList getItems() + { + return Lists.newArrayList(itemMap.values()); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + Map newMap; + QueryAttribute queryAttr; + int numItems, modelId; + + aStream.readVersion(0); + + myName = aStream.readString(); + + numItems = aStream.readInt(); + if (numItems != itemMap.size()) + throw new IOException("Mismatched attribute count. Expected: " + itemMap.size() + " Read: " + numItems); + + newMap = Maps.newLinkedHashMap(); + for (int c1 = 0; c1 < numItems; c1++) + { + modelId = aStream.readInt(); + + queryAttr = itemMap.get(modelId); + queryAttr.zioReadRaw(aStream); + + newMap.put(modelId, queryAttr); + } + + itemMap = newMap; + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + QueryAttribute queryAttr; + int numItems; + + aStream.writeVersion(0); + + aStream.writeString(myName); + + numItems = itemMap.size(); + aStream.writeInt(numItems); + + for (int aModelId : itemMap.keySet()) + { + queryAttr = itemMap.get(aModelId); + + aStream.writeInt(aModelId); + queryAttr.zioWriteRaw(aStream); + } + } + + @Override + public String toString() + { + return myName; + } + +} diff --git a/src/glum/gui/panel/itemList/query/QueryAttribute.java b/src/glum/gui/panel/itemList/query/QueryAttribute.java new file mode 100644 index 0000000..aad6e7a --- /dev/null +++ b/src/glum/gui/panel/itemList/query/QueryAttribute.java @@ -0,0 +1,150 @@ +package glum.gui.panel.itemList.query; + +import java.io.IOException; + +import javax.swing.JLabel; +import javax.swing.table.*; + +import glum.unit.EmptyUnitProvider; +import glum.unit.UnitProvider; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; + +public class QueryAttribute implements ZioRaw +{ + // State vars + public final int modelIndex; + public Enum refKey; + public Class refClass; + public UnitProvider refUnitProvider; + + // Config vars + public String label; + public boolean isVisible; + public int alignment; + public int defaultSize; + public int minSize; + public int maxSize; + public int sortDir; + + // Helper vars + public TableColumn assocTableColumn; + public TableCellRenderer renderer; + public TableCellEditor editor; + + public QueryAttribute(int aModelIndex) + { + modelIndex = aModelIndex; + refKey = null; + refClass = String.class; + label = ""; + + isVisible = true; + alignment = JLabel.LEFT; + defaultSize = 100; + maxSize = -1; + minSize = -1; + sortDir = 0; + refUnitProvider = new EmptyUnitProvider(); + + assocTableColumn = null; + renderer = null; + editor = null; + } + + public QueryAttribute(QueryAttribute aAttribute) + { + // Synchronize the attribute before copying the configuration + aAttribute.synchronizeAttribute(); + + modelIndex = aAttribute.modelIndex; + refKey = aAttribute.refKey; + refClass = aAttribute.refClass; + label = aAttribute.label; + + isVisible = aAttribute.isVisible; + alignment = aAttribute.alignment; + defaultSize = aAttribute.defaultSize; + maxSize = aAttribute.maxSize; + minSize = aAttribute.minSize; + sortDir = aAttribute.sortDir; + refUnitProvider = aAttribute.refUnitProvider; + + assocTableColumn = null; + renderer = null; //aAttribute.renderer; + editor = null; //aAttribute.editor; + } + + /** + * Sets this QueryAttribute to match aAttribute. + *

    + * Currently only the following config vars are matched: + * label, isVisible, alignment, defaultSize, sortDir + */ + public void setConfig(QueryAttribute aAttribute) + { + label = aAttribute.label; + isVisible = aAttribute.isVisible; + alignment = aAttribute.alignment; + defaultSize = aAttribute.defaultSize; + sortDir = aAttribute.sortDir; + } + + /** + * Synchronizes the QueryAttribute to it's associated column + */ + public void synchronizeAttribute() + { + if (assocTableColumn == null) + return; + + defaultSize = assocTableColumn.getWidth(); +// sortDir = assocTableColumn. + } + + /** + * Synchronizes the associated column to this QueryAttribute + */ + public void synchronizeColumn() + { + if (assocTableColumn == null) + return; + +System.out.println("Are we ready for this ???"); + assocTableColumn.setWidth(defaultSize); +// sortDir = assocTableColumn. + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + label = aStream.readString(); + isVisible = aStream.readBool(); + alignment = aStream.readInt(); + defaultSize = aStream.readInt(); + minSize = aStream.readInt(); + maxSize = aStream.readInt(); + sortDir = aStream.readInt(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + // Synchronize the attribute before serialization + synchronizeAttribute(); + + aStream.writeVersion(0); + + aStream.writeString(label); + aStream.writeBool(isVisible); + aStream.writeInt(alignment); + aStream.writeInt(defaultSize); + aStream.writeInt(minSize); + aStream.writeInt(maxSize); + aStream.writeInt(sortDir); + } + +} diff --git a/src/glum/gui/panel/itemList/query/QueryComposer.java b/src/glum/gui/panel/itemList/query/QueryComposer.java new file mode 100644 index 0000000..9e9a332 --- /dev/null +++ b/src/glum/gui/panel/itemList/query/QueryComposer.java @@ -0,0 +1,278 @@ +package glum.gui.panel.itemList.query; + +import java.util.*; +import javax.swing.*; +import javax.swing.table.*; + +import com.google.common.base.Preconditions; +import com.google.common.collect.*; + +import glum.unit.UnitProvider; + +public class QueryComposer> +{ + // State vars + protected ArrayList itemList; +// protected Map itemMap; + + public QueryComposer() + { + itemList = Lists.newArrayList(); +// itemMap = Maps.newLinkedHashMap(); + } + + /** + * Return the QueryAttribute located at aIndex + */ + public QueryAttribute get(int aIndex) + { + return itemList.get(aIndex); + } + + /** + * Return the QueryAttribute associated with aRefKey + */ + public QueryAttribute getItem(G1 aRefKey) + { + for (QueryAttribute aItem : itemList) + { + if (aItem.refKey == aRefKey) + return aItem; + } + + return null; + } + + /** + * Returns a listing of all the QueryAttributes that were composed + */ + public Collection getItems() + { + return Lists.newArrayList(itemList); + } + + /** + * Returns a listing of the items found in the keyArr + */ + public Collection getItems(G1... keyArr) + { + List rList; + + rList = Lists.newArrayListWithCapacity(keyArr.length); + for (G1 aEnum : keyArr) + rList.add(getItem(aEnum)); + + return rList; + } + + /** + * Returns a listing of the items found between sIndex, eIndex (inclusive) + */ + public Collection getItemsFrom(int sIndex, int eIndex) + { + List rList; + + rList = Lists.newArrayListWithCapacity((eIndex - sIndex) + 1); + for (int c1 = sIndex; c1 <= eIndex; c1++) + rList.add(itemList.get(c1)); + + return rList; + } + + /** + * Returns a listing of the items found between sKey, eKey (inclusive) + */ + public Collection getItemsFrom(G1 sKey, G1 eKey) + { + int sIndex, eIndex; + + // Locate the indexes for the coresponding elements + sIndex = eIndex = -1; + for (int c1 = 0; c1 < itemList.size(); c1++) + { + if (itemList.get(c1).refKey == sKey) + sIndex = c1; + if (itemList.get(c1).refKey == eKey) + eIndex = c1; + } + + // Insanity checks + if (sIndex == -1) + throw new RuntimeException("Failure. Key is not in composer: sKey: " + sKey); + if (eIndex == -1) + throw new RuntimeException("Failure. Key is not in composer: eKey: " + eKey); + if (sIndex > eIndex) + throw new RuntimeException("Failure: eKey: (" + eKey + ") appears before for sKey: (" + sKey + ")"); + + return getItemsFrom(sIndex, eIndex); + } + + /** + * Returns the num of items in the composition + */ + public int size() + { + return itemList.size(); + } + + /** + * Method to add a QueryAttribute to this container + */ + public QueryAttribute addAttribute(G1 aRefKey, UnitProvider aUnitProvider, String aName, String maxValue) + { + return addAttribute(aRefKey, aUnitProvider, aName, maxValue, true); + } + + public QueryAttribute addAttribute(G1 aRefKey, UnitProvider aUnitProvider, String aName, String maxValue, boolean isVisible) + { + QueryAttribute aAttribute; + + // Insanity check + Preconditions.checkNotNull(aUnitProvider); + + aAttribute = addAttribute(aRefKey, Double.class, aName, maxValue, isVisible); + aAttribute.refUnitProvider = aUnitProvider; + return aAttribute; + } + + public QueryAttribute addAttribute(G1 aRefKey, Class aClass, String aName, String maxValue) + { + return addAttribute(aRefKey, aClass, aName, maxValue, true); + } + + public QueryAttribute addAttribute(G1 aRefKey, Class aClass, String aName, String maxValue, boolean isVisible) + { + int maxSize; + + // Compute the maxSize + maxSize = Integer.MAX_VALUE; + if (maxValue != null) + maxSize = computeStringWidth(maxValue) + 15; + + return addAttribute(aRefKey, aClass, aName, maxSize, isVisible); + } + + public QueryAttribute addAttribute(G1 aRefKey, Class aClass, String aName, int aMaxSize) + { + return addAttribute(aRefKey, aClass, aName, aMaxSize, true); + } + + public QueryAttribute addAttribute(G1 aRefKey, Class aClass, String aLabel, int aMaxSize, boolean isVisible) + { + QueryAttribute aAttribute; + int defaultSize, minSize, maxSize; + + // Get the defaultSize + defaultSize = 15; + if (aLabel != null) + defaultSize = computeStringWidth(aLabel); + + minSize = 15; + maxSize = aMaxSize; + if (defaultSize < minSize) + defaultSize = minSize; + if (maxSize < defaultSize) + maxSize = defaultSize; + + // Set the defaultSize to be maxSize (unless it is ~infinite) + if (maxSize != Integer.MAX_VALUE) + defaultSize = maxSize; + + // Form the attribute + aAttribute = new QueryAttribute(itemList.size()); + aAttribute.refKey = aRefKey; + aAttribute.refClass = aClass; + aAttribute.label = aLabel; + aAttribute.defaultSize = defaultSize; + aAttribute.maxSize = maxSize; + aAttribute.minSize = minSize; + aAttribute.isVisible = isVisible; + + itemList.add(aAttribute); + return aAttribute; + } + + public QueryAttribute addAttribute(G1 aRefKey, Enum enumSet[], String aName) + { + return addAttribute(aRefKey, enumSet, aName, true); + } + + public QueryAttribute addAttribute(G1 aRefKey, Enum enumSet[], String aLabel, boolean isVisible) + { + QueryAttribute aAttribute; + int defaultSize, aSize; + + // Get the defaultSize + defaultSize = 10; + if (aLabel != null) + defaultSize = computeStringWidth(aLabel); + + for (Enum aEnum : enumSet) + { + aSize = computeStringWidth(aEnum.toString()); + if (aSize > defaultSize) + defaultSize = aSize; + } + + // Form the attribute + aAttribute = new QueryAttribute(itemList.size()); + aAttribute.refKey = aRefKey; + aAttribute.refClass = Enum.class; + aAttribute.label = aLabel; + aAttribute.defaultSize = defaultSize; + aAttribute.maxSize = defaultSize; + aAttribute.minSize = 15; + aAttribute.isVisible = isVisible; + + itemList.add(aAttribute); + return aAttribute; + } + + /** + * Method to set in a custom Editor for the QueryAttribute associated with aRefKey + */ + public void setEditor(G1 aRefKey, TableCellEditor aEditor) + { + for (QueryAttribute aItem : itemList) + { + if (aItem.refKey == aRefKey) + { + aItem.editor = aEditor; + return; + } + } + + throw new RuntimeException("No item found with the key:" + aRefKey); + } + + /** + * Method to set in a custom Renderer for the QueryAttribute associated with aRefKey + */ + public void setRenderer(G1 aRefKey, TableCellRenderer aRenderer) + { + for (QueryAttribute aItem : itemList) + { + if (aItem.refKey == aRefKey) + { + aItem.renderer = aRenderer; + return; + } + } + + throw new RuntimeException("No item found with the key:" + aRefKey); + } + + /** + * Utility method to compute the width of a string with the standard font + */ + protected int computeStringWidth(String aStr) + { + JLabel tmpL; + + tmpL = new JLabel(aStr); + tmpL.setFont((new JTextField()).getFont()); + + return tmpL.getPreferredSize().width + 5; + } + +} diff --git a/src/glum/gui/panel/itemList/query/QueryItemHandler.java b/src/glum/gui/panel/itemList/query/QueryItemHandler.java new file mode 100644 index 0000000..61de190 --- /dev/null +++ b/src/glum/gui/panel/itemList/query/QueryItemHandler.java @@ -0,0 +1,58 @@ +package glum.gui.panel.itemList.query; + +import java.util.*; + +import glum.database.QueryItem; +import glum.gui.panel.itemList.BasicItemHandler; + +public class QueryItemHandler> extends BasicItemHandler +{ + public QueryItemHandler(QueryComposer aComposer) + { + super(aComposer.getItems()); + } + + public QueryItemHandler(Collection aQueryAttrList) + { + super(aQueryAttrList); + } + + @Override + public Object getColumnValue(G1 aObj, int colNum) + { + QueryItem> aItem; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return null; + +// return aObj.getValue(fullAttributeList.get(colNum).refKey); + aItem = null; + if (aObj instanceof QueryItem) + { + aItem = (QueryItem>)aObj; + return aItem.getValue(fullAttributeList.get(colNum).refKey); + } + + return null; + } + + @Override + public void setColumnValue(G1 aObj, int colNum, Object aValue) + { + QueryItem> aItem; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return; + +// aObj.setValue(fullAttributeList.get(colNum).refKey, aValue); + aItem = null; + if (aObj instanceof QueryItem) + { + aItem = (QueryItem>)aObj; + aItem.setValue(fullAttributeList.get(colNum).refKey, aValue); + } + } + +} diff --git a/src/glum/gui/panel/itemList/query/QueryTableCellRenderer.java b/src/glum/gui/panel/itemList/query/QueryTableCellRenderer.java new file mode 100644 index 0000000..cf8e7cc --- /dev/null +++ b/src/glum/gui/panel/itemList/query/QueryTableCellRenderer.java @@ -0,0 +1,53 @@ +package glum.gui.panel.itemList.query; + +import java.awt.Component; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.*; + +import glum.unit.Unit; + +public class QueryTableCellRenderer extends DefaultTableCellRenderer +{ + // State vars + protected Unit myUnit; + + public QueryTableCellRenderer(QueryAttribute aAttribute) + { + super(); + + myUnit = null; + if (aAttribute != null) + { + myUnit = aAttribute.refUnitProvider.getUnit(); + setHorizontalAlignment(aAttribute.alignment); + } + } + + public QueryTableCellRenderer() + { + this(null); + } + + /** + * Sets in the Unit used to retrieve the actual textual values of the table cell data + */ + public void setUnit(Unit aUnit) + { + myUnit = aUnit; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel retLabel; + + // No special processing is needed if no unit specified + retLabel = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (myUnit == null) + return retLabel; + + retLabel.setText(myUnit.getString(value)); + return retLabel; + } + +} diff --git a/src/glum/gui/panel/nub/Action.java b/src/glum/gui/panel/nub/Action.java new file mode 100644 index 0000000..001f3de --- /dev/null +++ b/src/glum/gui/panel/nub/Action.java @@ -0,0 +1,15 @@ +package glum.gui.panel.nub; + +public enum Action +{ + None, + Move, + ResizeN, + ResizeS, + ResizeW, + ResizeE, + ResizeNW, + ResizeNE, + ResizeSW, + ResizeSE, +} diff --git a/src/glum/gui/panel/nub/HorizontalNub.java b/src/glum/gui/panel/nub/HorizontalNub.java new file mode 100644 index 0000000..37a78c5 --- /dev/null +++ b/src/glum/gui/panel/nub/HorizontalNub.java @@ -0,0 +1,317 @@ +package glum.gui.panel.nub; + +import glum.gui.panel.WaftPanel; +import glum.util.MathUtil; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import javax.swing.JComponent; +import javax.swing.JLayeredPane; +import javax.swing.SwingUtilities; + +public class HorizontalNub extends JComponent implements MouseListener, MouseMotionListener +{ + // Constants + private static final int DEFAULT_CORNER_DIM = 20; + + // Gui vars + protected JComponent rootComp; + protected WaftPanel waftPanel; + + // Hook vars + protected Dimension hookDim; + protected Point hookLoc; + protected Point hookPt; + + // State vars + protected Action action; + protected boolean isNorth; + protected int cornerDim; + + // Color vars + protected Color plainColor; + protected Color hookColor; + protected Color armedColor; + + public HorizontalNub(JComponent aRootComp, WaftPanel aWaftPanel, boolean aIsNorth) + { + super(); + + rootComp = aRootComp; + waftPanel = aWaftPanel; + + hookDim = null; + hookLoc = null; + hookPt = null; + + action = Action.None; + isNorth = aIsNorth; + cornerDim = DEFAULT_CORNER_DIM; + + plainColor = Color.GRAY; + hookColor = Color.RED.darker(); + armedColor = Color.RED.darker().darker(); + + // Register for events of interest + addMouseMotionListener(this); + addMouseListener(this); + + // Set the dimensions of this component + setMinimumSize(new Dimension(50, 6)); + setPreferredSize(new Dimension(50, 6)); + } + + /** + * Sets in whether there will be a sub nub to grab hold that will allow the end user to resize the associated + * WaftPanel. + */ + public void setResizable(boolean aBool) + { + cornerDim = DEFAULT_CORNER_DIM; + if (aBool == false) + cornerDim = 0; + + repaint(); + } + + @Override + public void mouseClicked(MouseEvent aEvent) + { + ; // Nothing to do + } + + @Override + public void mousePressed(MouseEvent aEvent) + { + Point mousePt; + + // Push the waftPanel to the top of the (stack) view + if (rootComp instanceof JLayeredPane) + ((JLayeredPane)rootComp).moveToFront(waftPanel); + + mousePt = aEvent.getPoint(); + action = computeMode(mousePt); + + hookDim = new Dimension(waftPanel.getSize()); + hookLoc = new Point(waftPanel.getLocation()); + hookPt = SwingUtilities.convertPoint(this, mousePt, rootComp); + + repaint(); + } + + @Override + public void mouseReleased(MouseEvent aEvent) + { + hookDim = null; + hookLoc = null; + hookPt = null; + + action = computeMode(aEvent.getPoint()); + repaint(); + } + + @Override + public void mouseEntered(MouseEvent aEvent) + { + ;// Nothing to do + } + + @Override + public void mouseExited(MouseEvent aEvent) + { + // Bail if we are in an active state + if (hookPt != null) + return; + + action = Action.None; + repaint(); + } + + @Override + public void mouseDragged(MouseEvent aEvent) + { + // Bail if we are not in an active state + if (hookPt == null) + return; + + // Transform the reference WaftPanel + tranformWaftPanel(aEvent); + } + + @Override + public void mouseMoved(MouseEvent aEvent) + { + action = computeMode(aEvent.getPoint()); + repaint(); + } + + @Override + public void paint(Graphics g) + { + Graphics2D g2d; + int width, height; + boolean isArmed; + + height = getHeight(); + width = getWidth(); + + g2d = (Graphics2D)g; + + // Left nub + if (cornerDim > 0) + { + isArmed = action == Action.ResizeNW || action == Action.ResizeSW; + drawHandle(g2d, 0, 0, cornerDim, height, isArmed); + + // Right nub + isArmed = action == Action.ResizeNE || action == Action.ResizeSE; + drawHandle(g2d, width-cornerDim, 0, cornerDim, height, isArmed); + } + + // Middle nub + drawHandle(g2d, cornerDim, 0, width - (2 * cornerDim), height, action == Action.Move); + } + + /** + * Helper method to update the mode based on the mouse position + */ + private Action computeMode(Point mousePt) + { + int mouseX, mouseY; + + mouseX = mousePt.x; + mouseY = mousePt.y; + + if (mouseX < 0 || mouseX >= getWidth()) + return Action.None; + if (mouseY < 0 || mouseY >= getHeight()) + return Action.None; + + if (isNorth == true) + { + if (mouseX < cornerDim) + return Action.ResizeNW; + else if (mouseX >= getWidth() - cornerDim) + return Action.ResizeNE; + } + else + { + if (mouseX < cornerDim) + return Action.ResizeSW; + else if (mouseX >= getWidth() - cornerDim) + return Action.ResizeSE; + } + + return Action.Move; + } + + /** + * Renders the control handle in the proper state + */ + protected void drawHandle(Graphics2D g2d, int x, int y, int width, int height, boolean isArmed) + { + Color aColor; + + aColor = plainColor; + if (isArmed == true) + { + aColor = armedColor; + if (hookPt != null) + aColor = hookColor; + } + + g2d.setColor(aColor); + g2d.fill3DRect(x, y, width, height, true); + } + + /** + * Helper method to transform the associated WaftPanel based on the drag event and the current action. + */ + protected void tranformWaftPanel(MouseEvent aEvent) + { + Dimension minDim, targDim; + Point targLoc; + Point mousePt; + Point nwPt, sePt; + int maxX, maxY; + int diffX, diffY; + + maxX = rootComp.getWidth(); + maxY = rootComp.getHeight(); + + minDim = waftPanel.getMinimumSize(); + + // Retrieve the mousePt in the rootComp coordinate system and force it to be in the bounds of the rootComp. + mousePt = aEvent.getPoint(); + mousePt = SwingUtilities.convertPoint(this, mousePt, rootComp); + mousePt.x = MathUtil.boundRange(0, maxX, mousePt.x); + mousePt.y = MathUtil.boundRange(0, maxY, mousePt.y); + + // Compute the total change in the mouse movement from the hooked point + diffX = -(mousePt.x - hookPt.x); + diffY = -(mousePt.y - hookPt.y); + + // Retrieve the northwest and southeast points in the root coordinate system + nwPt = new Point(0, 0); + nwPt = SwingUtilities.convertPoint(waftPanel, nwPt, rootComp); + sePt = new Point(waftPanel.getWidth(), waftPanel.getHeight()); + sePt = SwingUtilities.convertPoint(waftPanel, sePt, rootComp); + + // Determine the target transformations based on the action + if (action == Action.ResizeNW) + { + targLoc = new Point(hookLoc.x - diffX, hookLoc.y - diffY); + targDim = new Dimension(hookDim.width + diffX, hookDim.height + diffY); + + MathUtil.forceConstraints(targLoc, targDim, 0, 0, sePt.x, sePt.y, minDim, false); + } + else if (action == Action.ResizeNE) + { + targLoc = new Point(hookLoc.x, hookLoc.y - diffY); + targDim = new Dimension(hookDim.width - diffX, hookDim.height + diffY); + + MathUtil.forceConstraints(targLoc, targDim, nwPt.x, 0, maxX, sePt.y, minDim, false); + } + else if (action == Action.ResizeSW) + { + targLoc = new Point(hookLoc.x - diffX, hookLoc.y); + targDim = new Dimension(hookDim.width + diffX, hookDim.height - diffY); + + MathUtil.forceConstraints(targLoc, targDim, 0, nwPt.y, sePt.x, maxY, minDim, false); + } + else if (action == Action.ResizeSE) + { + targLoc = new Point(hookLoc.x, hookLoc.y); + targDim = new Dimension(hookDim.width - diffX, hookDim.height - diffY); + + MathUtil.forceConstraints(targLoc, targDim, nwPt.x, nwPt.y, maxX, maxY, minDim, false); + } + else if (action == Action.Move) + { + targDim = new Dimension(hookDim); + targLoc = new Point(hookLoc.x - diffX, hookLoc.y - diffY); + + targLoc.x = MathUtil.boundRange(0, maxX - targDim.width, targLoc.x); + targLoc.y = MathUtil.boundRange(0, maxY - targDim.height, targLoc.y); + } + else + { + throw new RuntimeException("Unsupported action: " + action); + } + + // Update the waftPanel with the target transformations + waftPanel.setLocation(targLoc); + waftPanel.setPreferredSize(targDim); + waftPanel.setSize(targDim); + +// rootComp.validate(); + rootComp.revalidate(); + } + +} diff --git a/src/glum/gui/panel/task/AutoTaskPanel.java b/src/glum/gui/panel/task/AutoTaskPanel.java new file mode 100644 index 0000000..2044665 --- /dev/null +++ b/src/glum/gui/panel/task/AutoTaskPanel.java @@ -0,0 +1,153 @@ +package glum.gui.panel.task; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GLabel; +import glum.gui.panel.generic.GenericCodes; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.TimeCountUnit; +import glum.unit.UnitProvider; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import net.miginfocom.swing.MigLayout; + +/** + * TaskPanel component that shows the progress of a Task. The panel itself is a Task. + * This TaskPanel will typically disappear once the progress reaches 100% or the task is + * no longer valid. This task may be stopped prematurely if the user hits the abort button. + */ +public class AutoTaskPanel extends BaseTaskPanel implements ActionListener, GenericCodes +{ + // GUI vars + private JButton abortB; + + public AutoTaskPanel(Component aParent) + { + this(aParent, false, true); + } + + public AutoTaskPanel(Component aParent, boolean hasInfoArea, boolean hasStatusArea) + { + super(aParent); + + buildGuiArea(hasInfoArea, hasStatusArea); + setSize(450, getPreferredSize().height); + setTitle("Task Progress"); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(abortB)); + + updateGui(); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == abortB) + { + abort(); + notifyListeners(this, ID_CANCEL, "Abort"); + setVisible(false); + } + + updateGui(); + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea(boolean hasInfoArea, boolean hasStatusArea) + { + JLabel tmpL; + JScrollPane tmpScrollPane; + UnitProvider percentUP, timerUP; + String colConstraints; + Font aFont; + Border aBorder; + + colConstraints = "[][]"; + if (hasStatusArea == true) + colConstraints += "[]"; + if (hasInfoArea == true) + colConstraints += "[grow][]"; + setLayout(new MigLayout("", "[right][pref!][grow][right][pref!]", colConstraints)); + aFont = (new JTextField()).getFont(); + + // Title area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Progress + Timer area + percentUP = new ConstUnitProvider(new NumberUnit("%", "%", 100.0, 2)); + tmpL = new JLabel("Progress:"); + progressL = new GLabel(percentUP, aFont, true); + add(tmpL, ""); + add(progressL, ""); + + timerUP = new ConstUnitProvider(new TimeCountUnit(2)); + tmpL = new JLabel("Time:"); + timerL = new GLabel(timerUP, aFont, true); + add(tmpL, "skip 1"); + add(timerL, "wrap"); + + // Status area + if (hasStatusArea == true) + { + tmpL = new JLabel("Status:"); + statusL = new GLabel(aFont); + add(tmpL, ""); + add(statusL, "growx,span,wrap"); + } + + // Info area + infoTA = null; + if (hasInfoArea == true) + { + infoTA = GuiUtil.createUneditableTextArea(3, 0); + infoTA.setFont(new Font("Monospaced", Font.PLAIN, aFont.getSize()-2)); + infoTA.setOpaque(true); + + tmpScrollPane = new JScrollPane(infoTA); + add(tmpScrollPane, "growx,growy,span,wrap"); + } + + // Control area + abortB = GuiUtil.createJButton("Abort", this, aFont); + add(abortB, "align right,span,split 1"); + + // Border + aBorder = new BevelBorder(BevelBorder.RAISED); + setBorder(aBorder); + } + + @Override + protected void updateGui() + { + // If progress >= 1.0, then we are done. + // Automatically hide this TaskPanel + if (isActive == false || mProgress >= 1.0) + { + isActive = false; + setVisible(false); + } + + progressL.setValue(mProgress); + if (statusL != null) + statusL.setValue(mStatus); + timerL.setValue(mTimer.getTotal()); + } + +} diff --git a/src/glum/gui/panel/task/BaseTaskPanel.java b/src/glum/gui/panel/task/BaseTaskPanel.java new file mode 100644 index 0000000..ec78b9a --- /dev/null +++ b/src/glum/gui/panel/task/BaseTaskPanel.java @@ -0,0 +1,273 @@ +package glum.gui.panel.task; + +import glum.gui.component.GLabel; +import glum.gui.panel.GlassPanel; +import glum.reflect.FunctionRunnable; +import glum.task.Task; +import glum.util.WallTimer; + +import java.awt.Component; +import java.awt.Font; + +import javax.swing.JLabel; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +/** + * Abstract TaskPanel that handles all of the state vars used to maintain + * the Task interface. + */ +public abstract class BaseTaskPanel extends GlassPanel implements Task +{ + // State vars + protected boolean isActive; + protected String infoMsgFrag; + protected double mProgress; + protected String mStatus; + protected WallTimer mTimer; + protected long oldTimeMs; + protected long refreshRateMs; + protected int maxLC; + + // Gui vars + protected JLabel titleL; + protected GLabel progressL, timerL; + protected GLabel statusL; + protected JTextArea infoTA; + + public BaseTaskPanel(Component aParent) + { + super(aParent); + + isActive = true; + infoMsgFrag = null; + mProgress = 0; + mStatus = ""; + mTimer = new WallTimer(true); + oldTimeMs = Long.MIN_VALUE; + refreshRateMs = 47; + maxLC = -1; + } + + /** + * Method to set the font of the infoTA + */ + public void setFontInfo(Font aFont) + { + if (infoTA != null) + infoTA.setFont(aFont); + } + + /** + * Sets in the maximum number of lines that will be displayed in the info area + */ + public void setMaxLineCount(int aMaxLC) + { + maxLC = aMaxLC; + } + + @Override + public void abort() + { + mTimer.stop(); + isActive = false; + SwingUtilities.invokeLater(new FunctionRunnable(this, "updateGui")); + } + + @Override + public void infoAppend(String aMsg) + { + infoUpdateForce(aMsg); + + // Reset the dynamic vars + infoMsgFrag = null; + oldTimeMs = Long.MIN_VALUE; + } + + @Override + public void infoAppendln(String aMsg) + { + infoAppend(aMsg + '\n'); + } + + @Override + public void infoUpdate(String aMsg) + { + // Bail if it is not time to update our UI + if (isTimeForUpdate() == false) + return; + + infoUpdateForce(aMsg); + } + + @Override + public double getProgress() + { + return mProgress; + } + + @Override + public void reset() + { + isActive = true; + infoMsgFrag = null; + mProgress = 0; + mStatus = ""; + mTimer.start(); + oldTimeMs = Long.MIN_VALUE; + + // Clear out all the text in the infoTA + if (infoTA != null) + infoTA.setText(""); + + SwingUtilities.invokeLater(new FunctionRunnable(this, "updateGui")); + } + + @Override + public void setProgress(double aProgress) + { + mProgress = aProgress; + + // Bail if it is not time to update our UI + if (isTimeForUpdate() == false && aProgress < 1.0) + return; + + SwingUtilities.invokeLater(new FunctionRunnable(this, "updateGui")); + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0)/ maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + refreshRateMs = aRateMs; + } + + @Override + public void setTabSize(int numSpaces) + { + if (infoTA != null) + infoTA.setTabSize(numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + titleL.setText(aTitle); + } + + @Override + public void setStatus(String aMsg) + { + mStatus = aMsg; + + // Bail if it is not time to update our UI + if (isTimeForUpdate() == false) + return; + + SwingUtilities.invokeLater(new FunctionRunnable(this, "updateGui")); + } + + @Override + public boolean isActive() + { + return isActive; + } + + /** + * Utility method that checks the update rate and returns true if the task + * UI can be updated. Note this method keeps track of the timer vars to + * honor refreshRateMs + */ + protected boolean isTimeForUpdate() + { + long currTimeMs, totalTimeMs; + + // Get the current time + currTimeMs = System.nanoTime() / 1000000; + + // Has enough time elapsed? + totalTimeMs = currTimeMs - oldTimeMs; + if (totalTimeMs < refreshRateMs && totalTimeMs > 0) + return false; + + oldTimeMs = currTimeMs; + return true; + } + + /** + * Utility method that does the actual updating of the previous info text with aMsg + */ + protected void infoUpdateForce(String aMsg) + { + int end, start; + int currLC; + + // Bail if there is no info area + if (infoTA == null) + return; + + // Update the old message + if (infoMsgFrag != null) + { + // Update the info message + start = end = 0; + try + { + end = infoTA.getLineEndOffset(infoTA.getLineCount()-1); + start = end - infoMsgFrag.length(); + infoTA.replaceRange(aMsg, start, end); + } + catch (Exception aExp) + { + System.out.println("infoMsgFrag:" + infoMsgFrag.length() + " start: " + start + " end:" + end); + throw new RuntimeException(aExp); + } + } + // Just append the message + else + { + infoTA.append(aMsg); + + // Trim the buffer if we exceed our maxLC + if (maxLC > 0) + { + currLC = infoTA.getLineCount(); + if (currLC > maxLC) + { + start = end = 0; + try + { + start = 0; + end = infoTA.getLineEndOffset(currLC - maxLC); + infoTA.replaceRange("", start, end); + } + catch (Exception aExp) + { + System.out.println("currLC:" + currLC + " maxLC:" + maxLC + " start: " + start + " end:" + end); + throw new RuntimeException(aExp); + } + } + } + } + + // Save off the new dynamic message fragment + infoMsgFrag = aMsg; + +// timerL.setValue(mTimer.getTotal()); +// SwingUtilities.invokeLater(new FunctionRunnable(timerL, "updateGui")); + + // Update our internal time + oldTimeMs = System.nanoTime() / 1000000; + } + + /** + * Utility method to update the GUI + */ + protected abstract void updateGui(); + +} diff --git a/src/glum/gui/panel/task/DualTaskPanel.java b/src/glum/gui/panel/task/DualTaskPanel.java new file mode 100644 index 0000000..256c0db --- /dev/null +++ b/src/glum/gui/panel/task/DualTaskPanel.java @@ -0,0 +1,106 @@ +package glum.gui.panel.task; + +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JSplitPane; +import javax.swing.JTextField; + +import net.miginfocom.swing.MigLayout; + +import glum.gui.GuiUtil; +import glum.gui.panel.GlassPanel; + +public class DualTaskPanel extends GlassPanel implements ActionListener +{ + // Gui vars + private PlainTaskPanel priTask, secTask; + private JButton abortB, closeB; + + public DualTaskPanel(Component parentFrame, PlainTaskPanel aPriTask, PlainTaskPanel aSecTask, boolean showControlArea) + { + super(parentFrame); + + priTask = aPriTask; + secTask = aSecTask; + + buildGui(showControlArea); + updateGui(); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == abortB) + { + priTask.abort(); + secTask.abort(); + } + else if (source == closeB) + { + setVisible(false); + } + + updateGui(); + } + + @Override + public void setVisible(boolean isVisible) + { + updateGui(); + + super.setVisible(isVisible); + } + + /** + * Forms the GUI + */ + private void buildGui(boolean showControlArea) + { + JSplitPane mainPane; + Font smallFont; + + mainPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, priTask, secTask); + mainPane.setResizeWeight(0.40); + + setLayout(new MigLayout("", "[grow]", "[grow][]")); + + // Main area + add(mainPane, "growx,growy,span 1,wrap"); + + // Control area + abortB = null; + closeB = null; + if (showControlArea == true) + { + smallFont = (new JTextField()).getFont(); + + abortB = GuiUtil.createJButton("Abort", this, smallFont); + add(abortB, "align right,split 2"); + + closeB = GuiUtil.createJButton("Close", this, smallFont); + add(closeB); + } + } + + /** + * Keeps the GUI synchronized + */ + private void updateGui() + { + boolean isActive; + + isActive = priTask.isActive | secTask.isActive(); + if (abortB != null && closeB != null) + { + abortB.setEnabled(isActive); + closeB.setEnabled(!isActive); + } + } + +} diff --git a/src/glum/gui/panel/task/FullTaskPanel.java b/src/glum/gui/panel/task/FullTaskPanel.java new file mode 100644 index 0000000..c130166 --- /dev/null +++ b/src/glum/gui/panel/task/FullTaskPanel.java @@ -0,0 +1,158 @@ +package glum.gui.panel.task; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.component.GLabel; +import glum.gui.panel.generic.GenericCodes; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.TimeCountUnit; +import glum.unit.UnitProvider; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import net.miginfocom.swing.MigLayout; + +/** + * TaskPanel component that shows the progress of a Task. The panel itself is a Task. + * This TaskPanel must be manually closed by the user. + */ +public class FullTaskPanel extends BaseTaskPanel implements ActionListener, GenericCodes +{ + // GUI vars + private JButton abortB, closeB; + + public FullTaskPanel(Component aParent) + { + this(aParent, true, true); + } + + public FullTaskPanel(Component aParent, boolean hasInfoArea, boolean hasStatusArea) + { + super(aParent); + + buildGuiArea(hasInfoArea, hasStatusArea); + setSize(450, getPreferredSize().height); + setTitle("Task Progress"); + + // Set up keyboard short cuts + FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(abortB)); + FocusUtil.addAncestorKeyBinding(this, "ENTER", new ClickAction(closeB)); + + updateGui(); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == abortB) + { + abort(); + notifyListeners(this, ID_CANCEL, "Abort"); + } + else if (source == closeB) + { + setVisible(false); + notifyListeners(this, ID_ACCEPT, "Close"); + } + + updateGui(); + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea(boolean hasInfoArea, boolean hasStatusArea) + { + JLabel tmpL; + JScrollPane tmpScrollPane; + UnitProvider percentUP, timerUP; + String colConstraints; + Font aFont; + Border aBorder; + + colConstraints = "[][]"; + if (hasStatusArea == true) + colConstraints += "[]"; + if (hasInfoArea == true) + colConstraints += "[grow][]"; + setLayout(new MigLayout("", "[right][pref!][grow][right][pref!]", colConstraints)); + aFont = (new JTextField()).getFont(); + + // Title area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Progress + Timer area + percentUP = new ConstUnitProvider(new NumberUnit("%", "%", 100.0, 2)); + tmpL = new JLabel("Progress:"); + progressL = new GLabel(percentUP, aFont, true); + add(tmpL, ""); + add(progressL, ""); + + timerUP = new ConstUnitProvider(new TimeCountUnit(2)); + tmpL = new JLabel("Time:"); + timerL = new GLabel(timerUP, aFont, true); + add(tmpL, "skip 1"); + add(timerL, "wrap"); + + // Status area + if (hasStatusArea == true) + { + tmpL = new JLabel("Status:"); + statusL = new GLabel(aFont); + add(tmpL, ""); + add(statusL, "growx,span,wrap"); + } + + // Info area + infoTA = null; + if (hasInfoArea == true) + { + infoTA = GuiUtil.createUneditableTextArea(7, 0); + infoTA.setFont(new Font("Monospaced", Font.PLAIN, aFont.getSize()-1)); + infoTA.setOpaque(true); + + tmpScrollPane = new JScrollPane(infoTA); + add(tmpScrollPane, "growx,growy,span,wrap"); + } + + // Control area + abortB = GuiUtil.createJButton("Abort", this, aFont); + closeB = GuiUtil.createJButton("Close", this, aFont); + add(abortB, "align right,span,split 2"); + add(closeB, "span 1"); + + // Border + aBorder = new BevelBorder(BevelBorder.RAISED); + setBorder(aBorder); + } + + @Override + protected void updateGui() + { + // If progress >= 1.0, then we are done + if (mProgress >= 1.0) + isActive = false; + + abortB.setEnabled(isActive); + closeB.setEnabled(!isActive); + + progressL.setValue(mProgress); + if (statusL != null) + statusL.setValue(mStatus); + timerL.setValue(mTimer.getTotal()); + } + +} diff --git a/src/glum/gui/panel/task/PlainTaskPanel.java b/src/glum/gui/panel/task/PlainTaskPanel.java new file mode 100644 index 0000000..1829e27 --- /dev/null +++ b/src/glum/gui/panel/task/PlainTaskPanel.java @@ -0,0 +1,114 @@ +package glum.gui.panel.task; + +import glum.gui.GuiUtil; +import glum.gui.component.GLabel; +import glum.unit.ConstUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.TimeCountUnit; +import glum.unit.UnitProvider; +import java.awt.Component; +import java.awt.Font; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import net.miginfocom.swing.MigLayout; + +/** + * TaskPanel component that shows the progress of a Task. The panel itself is a Task. + * This TaskPanel is typically embedded into other Components, thus it has no built in + * abort or close button. + */ +public class PlainTaskPanel extends BaseTaskPanel +{ + public PlainTaskPanel(Component aParent) + { + this(aParent, true, true); + } + + public PlainTaskPanel(Component aParent, boolean hasInfoArea, boolean hasStatusArea) + { + super(aParent); + + buildGuiArea(hasInfoArea, hasStatusArea); + setSize(450, getPreferredSize().height); + setTitle("Task Progress"); + + updateGui(); + } + + /** + * Forms the actual GUI + */ + protected void buildGuiArea(boolean hasInfoArea, boolean hasStatusArea) + { + JLabel tmpL; + JScrollPane tmpScrollPane; + UnitProvider percentUP, timerUP; + String colConstraints; + Font aFont; + + colConstraints = "[][]"; + if (hasStatusArea == true) + colConstraints += "[]"; + if (hasInfoArea == true) + colConstraints += "[grow]"; + setLayout(new MigLayout("", "[right][pref!][grow][right][pref!]", colConstraints)); + aFont = (new JTextField()).getFont(); + + // Title area + titleL = new JLabel("Title", JLabel.CENTER); + add(titleL, "growx,span,wrap"); + + // Progress + Timer area + percentUP = new ConstUnitProvider(new NumberUnit("%", "%", 100.0, 2)); + tmpL = new JLabel("Progress:"); + progressL = new GLabel(percentUP, aFont, true); + add(tmpL, ""); + add(progressL, ""); + + timerUP = new ConstUnitProvider(new TimeCountUnit(2)); + tmpL = new JLabel("Time:"); + timerL = new GLabel(timerUP, aFont, true); + add(tmpL, "skip 1"); + add(timerL, ""); + + // Status area + if (hasStatusArea == true) + { + tmpL = new JLabel("Status:"); + statusL = new GLabel(aFont); + add(tmpL, "newline"); + add(statusL, "growx,span"); + } + + // Info area + infoTA = null; + if (hasInfoArea == true) + { + infoTA = GuiUtil.createUneditableTextArea(7, 0); +infoTA.setFont(new Font(aFont.getName(), Font.PLAIN, aFont.getSize()-2)); + infoTA.setOpaque(true); + + tmpScrollPane = new JScrollPane(infoTA); + add(tmpScrollPane, "growx,growy,newline,span"); + } + + // Border + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + + @Override + protected void updateGui() + { + // If progress >= 1.0, then we are done + if (mProgress >= 1.0) + isActive = false; + + progressL.setValue(mProgress); + if (statusL != null) + statusL.setValue(mStatus); + timerL.setValue(mTimer.getTotal()); + } + +} diff --git a/src/glum/gui/table/JTableScrolling.java b/src/glum/gui/table/JTableScrolling.java new file mode 100644 index 0000000..1d3d21a --- /dev/null +++ b/src/glum/gui/table/JTableScrolling.java @@ -0,0 +1,266 @@ +/* +* Source from: http://www.chka.de/swing/table/JTableScrolling.java +* +*/ + +package glum.gui.table; + +import java.awt.Rectangle; +import java.awt.Insets; +import javax.swing.JTable; + + +public abstract class JTableScrolling +{ + private JTableScrolling() + { + } + + + public static Rectangle getRowBounds(JTable table, int row) + { + checkRow(table, row); + + Rectangle result = table.getCellRect(row, -1, true); + Insets i = table.getInsets(); + + result.x = i.left; + result.width = table.getWidth() - i.left - i.right; + + return result; + } + + public static Rectangle getRowBounds(JTable table, int first, int last) + { + checkRows(table, first, last); + + Rectangle result = table.getCellRect(first, -1, true); + result = result.union(table.getCellRect(last, -1, true)); + Insets i = table.getInsets(); + + result.x = i.left; + result.width = table.getWidth() - i.left - i.right; + + return result; + } + + + public static Rectangle getColumnBounds(JTable table, int column) + { + checkColumn(table, column); + + Rectangle result = table.getCellRect(-1, column, true); + Insets i = table.getInsets(); + + result.y = i.top; + result.height = table.getHeight() - i.top - i.bottom; + + return result; + } + + public static Rectangle getColumnBounds(JTable table, int first, int last) + { + checkColumns(table, first, last); + + Rectangle result = table.getCellRect(-1, first, true); + result = result.union(table.getCellRect(-1, last, true)); + Insets i = table.getInsets(); + + result.y = i.top; + result.height = table.getHeight() - i.top - i.bottom; + + return result; + } + + + /** For completeness. Only allows valid rows/columns. */ + public static Rectangle getCellBounds(JTable table, int row, int column) + { + checkCell(table, row, column); + + return table.getCellRect(row, column, true); + } + + public static Rectangle getCellBounds(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn) + { + checkCells(table, firstRow, lastRow, firstColumn, lastColumn); + + Rectangle result = table.getCellRect(firstRow, firstColumn, true); + return result.union(table.getCellRect(lastRow, lastColumn, true)); + } + + + public static void makeRowVisible(JTable table, int row) + { + Scrolling.scrollVertically(table, getRowBounds(table, row)); + } + + + public static void makeColumnVisible(JTable table, int column) + { + Scrolling.scrollHorizontally(table, getColumnBounds(table, column)); + } + + + public static void makeRowsVisible(JTable table, int first, int last) + { + Scrolling.scrollVertically(table, getRowBounds(table, first, last)); + } + + + public static void makeRowsVisible(JTable table, int first, int last, int bias) + { + Scrolling.scrollVertically(table, getRowBounds(table, first, last), bias); + } + + public static void makeColumnsVisible(JTable table, int first, int last) + { + Scrolling.scrollHorizontally(table, getColumnBounds(table, first, last)); + } + + public static void makeColumnsVisible(JTable table, int first, int last, int bias) + { + Scrolling.scrollHorizontally(table, getColumnBounds(table, first, last), bias); + } + + public static void makeCellsVisible(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn) + { + table.scrollRectToVisible(getCellBounds(table, firstRow, lastRow, firstColumn, lastColumn)); + } + + public static void makeCellsVisible(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn, int bias) + { + Scrolling.scroll(table, getCellBounds(table, firstRow, lastRow, firstColumn, lastColumn), bias); + } + + + public static void makeCellsVisible(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn, int rowBias, int columnBias) + { + Scrolling.scroll(table, getCellBounds(table, firstRow, lastRow, firstColumn, lastColumn), rowBias, columnBias); + } + + + + + public static void centerRow(JTable table, int row) + { + Scrolling.centerVertically(table, getRowBounds(table, row), false); + } + + + public static void centerColumn(JTable table, int column) + { + Scrolling.centerHorizontally(table, getColumnBounds(table, column), false); + } + + + public static void centerRows(JTable table, int first, int last) + { + Scrolling.centerVertically(table, getRowBounds(table, first, last), false); + } + + public static void centerColumns(JTable table, int first, int last) + { + Scrolling.centerHorizontally(table, getColumnBounds(table, first, last), false); + } + + public static void centerCell(JTable table, int row, int column) + { + Scrolling.center(table, getCellBounds(table, row, column), false); + } + + public static void centerCells(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn) + { + Scrolling.center(table, getCellBounds(table, firstRow, lastRow, firstColumn, lastColumn), false); + } + + + + + + + public static boolean isRowVisible(JTable table, int row) + { + return Scrolling.isVerticallyVisible(table, getRowBounds(table, row)); + } + + public static boolean isColumnVisible(JTable table, int column) + { + return Scrolling.isHorizontallyVisible(table, getColumnBounds(table, column)); + } + + public static boolean isCellVisible(JTable table, int row, int column) + { + return Scrolling.isVisible(table, getCellBounds(table, row, column)); + } + + public static boolean areColumnsVisible(JTable table, int first, int last) + { + return Scrolling.isHorizontallyVisible(table, getColumnBounds(table, first, last)); + } + + public static boolean areRowsVisible(JTable table, int first, int last) + { + return Scrolling.isVerticallyVisible(table, getRowBounds(table, first, last)); + } + + public static boolean areCellsVisible(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn) + { + checkCells(table, firstRow, lastRow, firstColumn, lastColumn); + + return Scrolling.isVisible(table, getCellBounds(table, firstRow, lastRow, firstColumn, lastColumn)); + } + + + + private static void checkRow(JTable table, int row) + { + if (row < 0) + throw new IndexOutOfBoundsException(row+" < 0"); + if (row >= table.getRowCount()) + throw new IndexOutOfBoundsException(row+" >= "+table.getRowCount()); + } + + private static void checkColumn(JTable table, int column) + { + if (column < 0) + throw new IndexOutOfBoundsException(column+" < 0"); + if (column >= table.getColumnCount()) + throw new IndexOutOfBoundsException(column+" >= "+table.getColumnCount()); + } + + private static void checkCell(JTable table, int row, int column) + { + checkRow(table, row); + checkColumn(table, column); + } + + + private static void checkRows(JTable table, int first, int last) + { + if (first < 0) + throw new IndexOutOfBoundsException(first+" < 0"); + if (first > last) + throw new IndexOutOfBoundsException(first+" > "+last); + if (last >= table.getRowCount()) + throw new IndexOutOfBoundsException(last+" >= "+table.getRowCount()); + } + + + private static void checkColumns(JTable table, int first, int last) + { + if (first < 0) + throw new IndexOutOfBoundsException(first+" < 0"); + if (first > last) + throw new IndexOutOfBoundsException(first+" > "+last); + if (last >= table.getColumnCount()) + throw new IndexOutOfBoundsException(last+" >= "+table.getColumnCount()); + } + + + private static void checkCells(JTable table, int firstRow, int lastRow, int firstColumn, int lastColumn) + { + checkRows(table, firstRow, lastRow); + checkColumns(table, firstColumn, lastColumn); + } +} diff --git a/src/glum/gui/table/KeyValueTableModel.java b/src/glum/gui/table/KeyValueTableModel.java new file mode 100644 index 0000000..6c51b33 --- /dev/null +++ b/src/glum/gui/table/KeyValueTableModel.java @@ -0,0 +1,117 @@ +package glum.gui.table; + +import java.util.*; +import javax.swing.table.AbstractTableModel; + +import com.google.common.collect.Lists; + +public class KeyValueTableModel extends AbstractTableModel +{ + private String[] columnNames; + private List> myList; + + /** + * Constructor + */ + public KeyValueTableModel() + { + this(null, null); + } + + public KeyValueTableModel(String keyHeader, String valueHeader) + { + columnNames = new String[2]; + columnNames[0] = ""; + columnNames[1] = ""; + + if (keyHeader != null) + columnNames[0] = keyHeader; + + if (valueHeader != null) + columnNames[1] = valueHeader; + + myList = Lists.newArrayList(); + } + + /** + * Adds the collection to our TableModel + */ + public void addItems(Map aMap) + { + int startIndex, endIndex; + + if (aMap.isEmpty() == true) + return; + + startIndex = myList.size(); + endIndex = startIndex + aMap.size(); + + myList.addAll(aMap.entrySet()); + + fireTableRowsInserted(startIndex, endIndex); + } + + /** + * Removes all values from the TableModel + */ + public void clear() + { + int endIndex; + + if (myList.isEmpty() == true) + return; + + endIndex = myList.size() - 1; + myList.clear(); + + fireTableRowsDeleted(0, endIndex); + } + + @Override + public int getColumnCount() + { + return columnNames.length; + } + + @Override + public int getRowCount() + { + return myList.size(); + } + + @Override + public String getColumnName(int col) + { + return columnNames[col]; + } + + @Override + public Object getValueAt(int row, int col) + { + if (row < 0 || row >= myList.size()) + return null; + + // DataProviderShell Enabled + if (col == 0) + return myList.get(row).getKey(); + else if (col == 1) + return myList.get(row).getValue(); + + return null; + } + + @Override + public Class getColumnClass(int col) + { + return String.class; + } + + @Override + public boolean isCellEditable(int row, int col) + { + // Note that the data/cell address is constant, + // no matter where the cell appears on screen. + return false; + } + +} diff --git a/src/glum/gui/table/Scrolling.java b/src/glum/gui/table/Scrolling.java new file mode 100644 index 0000000..3432b98 --- /dev/null +++ b/src/glum/gui/table/Scrolling.java @@ -0,0 +1,392 @@ +/* +* Source from: http://www.chka.de/swing/table/JTableScrolling.java +* +*/ + +package glum.gui.table; + +import java.awt.Rectangle; +import java.awt.Insets; +import javax.swing.JComponent; + +public abstract class Scrolling +{ + public static final int + VIEWPORT = 0, // take the policy of the viewport + UNCHANGED = 1, // don't scroll if it fills the visible area, otherwise take the policy of the viewport + FIRST = 2, // scroll the first part of the region into view + CENTER = 3, // center the region + LAST = 4; // scroll the last part of the region into view + + public static final int + NONE = 0, + TOP = 1, + VCENTER = 2, + BOTTOM = 4, + LEFT = 8, + HCENTER = 16, + RIGHT = 32; + + + private static final Insets + EMPTY_INSETS = new Insets(0, 0, 0, 0); + + + private Scrolling() + { + } + + public static void scroll(JComponent c, int part) + { + scroll(c, part & (LEFT|HCENTER|RIGHT), part & (TOP|VCENTER|BOTTOM)); + } + + public static void scroll(JComponent c, int horizontal, int vertical) + { + Rectangle visible = c.getVisibleRect(); + Rectangle bounds = c.getBounds(); + + switch (vertical) + { + case TOP: visible.y = 0; break; + case VCENTER: visible.y = (bounds.height - visible.height) / 2; break; + case BOTTOM: visible.y = bounds.height - visible.height; break; + } + + switch (horizontal) + { + case LEFT: visible.x = 0; break; + case HCENTER: visible.x = (bounds.width - visible.width) / 2; break; + case RIGHT: visible.x = bounds.width - visible.width; break; + } + + c.scrollRectToVisible(visible); + } + + + /*--------------------------------------------------------------- + Scrolling with bias. + */ + public static void scroll(JComponent c, Rectangle r, int bias) + { + scroll(c, r, bias, bias); + } + + public static void scroll(JComponent c, Rectangle r, int horizontalBias, int verticalBias) + { + Rectangle visible = c.getVisibleRect(), + dest = new Rectangle(r); + + if (dest.width > visible.width) + { + if (horizontalBias == VIEWPORT) + { + // leave as is + } + else if (horizontalBias == UNCHANGED) + { + if (dest.x <= visible.x && dest.x + dest.width >= visible.x + visible.width) + { + dest.x = visible.x; + dest.width = visible.width; + } + } + else + { + if (horizontalBias == CENTER) + dest.x += (dest.width - visible.width) / 2; + else if (horizontalBias == LAST) + dest.x += dest.width - visible.width; + + dest.width = visible.width; + } + } + + if (dest.height > visible.height) + { + if (verticalBias == VIEWPORT) + { + // leave as is + } + else if (verticalBias == UNCHANGED) + { + if (dest.y <= visible.y && dest.y + dest.height >= visible.y + visible.height) + { + dest.y = visible.y; + dest.height = visible.height; + } + } + else + { + if (verticalBias == CENTER) + dest.y += (dest.height - visible.height) / 2; + else if (verticalBias == LAST) + dest.y += dest.height - visible.height; + + dest.height = visible.height; + } + } + + if (!visible.contains(dest)) + c.scrollRectToVisible(dest); + } + + + /*-------------------------------------------------------- + One-direction scrolling. + */ + + public static void scrollHorizontally(JComponent c, Rectangle r) + { + scrollHorizontally(c, r.x, r.x + r.width); + } + + public static void scrollHorizontally(JComponent c, int from, int to) + { + Rectangle visible = c.getVisibleRect(); + + if (visible.x <= from && visible.x + visible.width >= to) + return; + + visible.x = from; + visible.width = to - from; + + c.scrollRectToVisible(visible); + } + + public static void scrollHorizontally(JComponent c, Rectangle r, int bias) + { + scrollHorizontally(c, r.x, r.x + r.width, bias); + } + + public static void scrollHorizontally(JComponent c, int from, int to, int bias) + { + Rectangle visible = c.getVisibleRect(), + dest = new Rectangle(visible); + + dest.x = from; + dest.width = to - from; + + if (dest.width > visible.width) + { + if (bias == VIEWPORT) + { + // leave as is + } + else if (bias == UNCHANGED) + { + if (dest.x <= visible.x && dest.x + dest.width >= visible.x + visible.width) + { + dest.x = visible.x; + dest.width = visible.width; + } + } + else + { + if (bias == CENTER) + dest.x += (dest.width - visible.width) / 2; + else if (bias == LAST) + dest.x += dest.width - visible.width; + + dest.width = visible.width; + } + } + + if (!visible.contains(dest)) + c.scrollRectToVisible(dest); + } + + + + public static void scrollVertically(JComponent c, Rectangle r) + { + scrollVertically(c, r.y, r.y + r.height); + } + + public static void scrollVertically(JComponent c, int from, int to) + { + Rectangle visible = c.getVisibleRect(); + + if (visible.y <= from && visible.y + visible.height >= to) + return; + + visible.y = from; + visible.height = to - from; + + c.scrollRectToVisible(visible); + } + + public static void scrollVertically(JComponent c, Rectangle r, int bias) + { + scrollVertically(c, r.y, r.y + r.height, bias); + } + + public static void scrollVertically(JComponent c, int from, int to, int bias) + { + Rectangle visible = c.getVisibleRect(), + dest = new Rectangle(visible); + + dest.y = from; + dest.height = to - from; + + if (dest.height > visible.height) + { + if (bias == VIEWPORT) + { + // leave as is + } + else if (bias == UNCHANGED) + { + if (dest.y <= visible.y && dest.y + dest.height >= visible.y + visible.height) + { + dest.y = visible.y; + dest.height = visible.height; + } + } + else + { + if (bias == CENTER) + dest.y += (dest.height - visible.height) / 2; + else if (bias == LAST) + dest.y += dest.height - visible.height; + + dest.height = visible.height; + } + } + + if (!visible.contains(dest)) + c.scrollRectToVisible(dest); + } + + /*---------------------------------------------------------- + Centering. + */ + + public static void center(JComponent c, Rectangle r, boolean withInsets) + { + Rectangle visible = c.getVisibleRect(); + + visible.x = r.x - (visible.width - r.width) / 2; + visible.y = r.y - (visible.height - r.height) / 2; + + Rectangle bounds = c.getBounds(); + Insets i = withInsets ? EMPTY_INSETS : c.getInsets(); + bounds.x = i.left; + bounds.y = i.top; + bounds.width -= i.left + i.right; + bounds.height -= i.top + i.bottom; + + if (visible.x < bounds.x) + visible.x = bounds.x; + + if (visible.x + visible.width > bounds.x + bounds.width) + visible.x = bounds.x + bounds.width - visible.width; + + if (visible.y < bounds.y) + visible.y = bounds.y; + + if (visible.y + visible.height > bounds.y + bounds.height) + visible.y = bounds.y + bounds.height - visible.height; + + c.scrollRectToVisible(visible); + } + + + public static void centerHorizontally(JComponent c, Rectangle r, boolean withInsets) + { + centerHorizontally(c, r.x, r.x + r.width, withInsets); + } + + public static void centerHorizontally(JComponent c, int from, int to, boolean withInsets) + { + Rectangle bounds = c.getBounds(); + Insets i = withInsets ? EMPTY_INSETS : c.getInsets(); + bounds.x = i.left; + bounds.y = i.top; + bounds.width -= i.left + i.right; + bounds.height -= i.top + i.bottom; + + Rectangle visible = c.getVisibleRect(); + + visible.x = from - (visible.width + from - to) / 2; + + if (visible.x < bounds.x) + visible.x = bounds.x; + + if (visible.x + visible.width > bounds.x + bounds.width) + visible.x = bounds.x + bounds.width - visible.width; + + c.scrollRectToVisible(visible); + } + + public static void centerVertically(JComponent c, Rectangle r, boolean withInsets) + { + centerVertically(c, r.y, r.y + r.height, withInsets); + } + + public static void centerVertically(JComponent c, int from, int to, boolean withInsets) + { + Rectangle bounds = c.getBounds(); + Insets i = withInsets ? EMPTY_INSETS : c.getInsets(); + bounds.x = i.left; + bounds.y = i.top; + bounds.width -= i.left + i.right; + bounds.height -= i.top + i.bottom; + + Rectangle visible = c.getVisibleRect(); + + visible.y = from - (visible.height + from - to) / 2; + + if (visible.y < bounds.y) + visible.y = bounds.y; + + if (visible.y + visible.height > bounds.y + bounds.height) + visible.y = bounds.y + bounds.height - visible.height; + + c.scrollRectToVisible(visible); + } + + /*----------------------------------------------------------- + Visibility. + */ + + public static boolean isVisible(JComponent c, Rectangle r) + { + return c.getVisibleRect().contains(r); + } + + public static boolean isHorizontallyVisible(JComponent c, int from, int to) + { + Rectangle visible = c.getVisibleRect(); + + return visible.x <= from + && visible.x + visible.width >= to; + } + + + public static boolean isHorizontallyVisible(JComponent c, Rectangle r) + { + Rectangle visible = c.getVisibleRect(); + + return visible.x <= r.x + && visible.x + visible.width >= r.x + r.width; + } + + + public static boolean isVerticallyVisible(JComponent c, int from, int to) + { + Rectangle visible = c.getVisibleRect(); + + return visible.y <= from + && visible.y + visible.height >= to; + } + + + public static boolean isVerticallyVisible(JComponent c, Rectangle r) + { + Rectangle visible = c.getVisibleRect(); + + return visible.y <= r.y + && visible.y + visible.height >= r.y + r.height; + } +} diff --git a/src/glum/gui/table/TableSorter.java b/src/glum/gui/table/TableSorter.java new file mode 100644 index 0000000..83f99ea --- /dev/null +++ b/src/glum/gui/table/TableSorter.java @@ -0,0 +1,617 @@ +package glum.gui.table; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.*; + +/** + * TableSorter is a decorator for TableModels; adding sorting functionality to a + * supplied TableModel. TableSorter does not store or copy the data in its + * TableModel; instead it maintains a map from the row indexes of the view to + * the row indexes of the model. As requests are made of the sorter (like + * getValueAt(row, col)) they are passed to the underlying model after the row + * numbers have been translated via the internal mapping array. This way, the + * TableSorter appears to hold another copy of the table with the rows in a + * different order. + *

    + * TableSorter registers itself as a listener to the underlying model, just as + * the JTable itself would. Events recieved from the model are examined, + * sometimes manipulated (typically widened), and then passed on to the + * TableSorter's listeners (typically the JTable). If a change to the model has + * invalidated the order of TableSorter's rows, a note of this is made and the + * sorter will resort the rows the next time a value is requested. + *

    + * When the tableHeader property is set, either by using the setTableHeader() + * method or the two argument constructor, the table header may be used as a + * complete UI for TableSorter. The default renderer of the tableHeader is + * decorated with a renderer that indicates the sorting status of each column. + * In addition, a mouse listener is installed with the following behavior: + *

      + *
    • + * Mouse-click: Clears the sorting status of all other columns and advances the + * sorting status of that column through three values: {NOT_SORTED, ASCENDING, + * DESCENDING} (then back to NOT_SORTED again). + *
    • + * SHIFT-mouse-click: Clears the sorting status of all other columns and cycles + * the sorting status of the column through the same three values, in the + * opposite order: {NOT_SORTED, DESCENDING, ASCENDING}. + *
    • + * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the + * changes to the column do not cancel the statuses of columns that are already + * sorting - giving a way to initiate a compound sort. + *
    + *

    + * This is a long overdue rewrite of a class of the same name that first + * appeared in the swing table demos in 1997. + * + * @author Philip Milne + * @author Brendon McLean + * @author Dan van Enckevort + * @author Parwinder Sekhon + * @version 2.0 02/27/04 + */ +@SuppressWarnings("unchecked") +// This class is no longer supported. It should be removed as of Java 1.7 +public class TableSorter extends AbstractTableModel +{ + protected TableModel tableModel; + + // Constants + private static final long serialVersionUID = -1999L; + public static final int DESCENDING = -1; + public static final int NOT_SORTED = 0; + public static final int ASCENDING = 1; + + private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED); + + public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() + { + @Override + public int compare(Object o1, Object o2) + { + return ((Comparable)o1).compareTo(o2); + } + }; + public static final Comparator LEXICAL_COMPARATOR = new Comparator() + { + @Override + public int compare(Object o1, Object o2) + { + return o1.toString().compareTo(o2.toString()); + } + }; + + private Row[] viewToModel; + private int[] modelToView; + + private JTableHeader tableHeader; + private MouseListener mouseListener; + private TableModelListener tableModelListener; + private Map columnComparators = new HashMap(); + private List sortingColumns = new ArrayList(); + private boolean isSortEnabled; + + public TableSorter() + { + this.mouseListener = new MouseHandler(); + this.tableModelListener = new TableModelHandler(); + isSortEnabled = true; + } + + public TableSorter(TableModel tableModel) + { + this(); + setTableModel(tableModel); + } + + public TableSorter(TableModel tableModel, JTableHeader tableHeader) + { + this(); + setTableHeader(tableHeader); + setTableModel(tableModel); + } + + private void clearSortingState() + { + viewToModel = null; + modelToView = null; + } + + public TableModel getTableModel() + { + return tableModel; + } + + public void setTableModel(TableModel tableModel) + { + if (this.tableModel != null) + { + this.tableModel.removeTableModelListener(tableModelListener); + } + + this.tableModel = tableModel; + if (this.tableModel != null) + { + this.tableModel.addTableModelListener(tableModelListener); + } + + clearSortingState(); + fireTableStructureChanged(); + } + + public JTableHeader getTableHeader() + { + return tableHeader; + } + + public void setTableHeader(JTableHeader tableHeader) + { + if (this.tableHeader != null) + { + this.tableHeader.removeMouseListener(mouseListener); + TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer(); + if (defaultRenderer instanceof SortableHeaderRenderer) + { + this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer)defaultRenderer).tableCellRenderer); + } + } + this.tableHeader = tableHeader; + if (this.tableHeader != null) + { + this.tableHeader.addMouseListener(mouseListener); + this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer())); + } + } + + public boolean isSorting() + { + return sortingColumns.size() != 0; + } + + private Directive getDirective(int column) + { + for (int i = 0; i < sortingColumns.size(); i++) + { + Directive directive = (Directive)sortingColumns.get(i); + if (directive.column == column) + { + return directive; + } + } + return EMPTY_DIRECTIVE; + } + + public int getSortingStatus(int column) + { + return getDirective(column).direction; + } + + private void sortingStatusChanged() + { + clearSortingState(); + fireTableDataChanged(); + if (tableHeader != null) + { + tableHeader.repaint(); + } + } + + public void setSortingStatus(int column, int status) + { + Directive directive = getDirective(column); + if (directive != EMPTY_DIRECTIVE) + { + sortingColumns.remove(directive); + } + if (status != NOT_SORTED) + { + sortingColumns.add(new Directive(column, status)); + } + sortingStatusChanged(); + } + + protected Icon getHeaderRendererIcon(int column, int size) + { + Directive directive = getDirective(column); + if (directive == EMPTY_DIRECTIVE) + { + return null; + } + // return new Arrow(directive.direction == DESCENDING, size, + // sortingColumns.indexOf(directive)); + return null; + } + + private void cancelSorting() + { + sortingColumns.clear(); + sortingStatusChanged(); + } + + public void setColumnComparator(Class type, Comparator comparator) + { + if (comparator == null) + { + columnComparators.remove(type); + } + else + { + columnComparators.put(type, comparator); + } + } + + protected Comparator getComparator(int column) + { + Class columnType = tableModel.getColumnClass(column); + Comparator comparator = (Comparator)columnComparators.get(columnType); + if (comparator != null) + { + return comparator; + } + if (Comparable.class.isAssignableFrom(columnType)) + { + return COMPARABLE_COMAPRATOR; + } + return LEXICAL_COMPARATOR; + } + + private Row[] getViewToModel() + { + if (viewToModel == null) + { + int tableModelRowCount = tableModel.getRowCount(); + viewToModel = new Row[tableModelRowCount]; + for (int row = 0; row < tableModelRowCount; row++) + { + viewToModel[row] = new Row(row); + } + + if (isSorting()) + { + Arrays.sort(viewToModel); + } + } + return viewToModel; + } + + public int modelIndex(int viewIndex) + { + return getViewToModel()[viewIndex].modelIndex; + } + + private int[] getModelToView() + { + if (modelToView == null) + { + int n = getViewToModel().length; + modelToView = new int[n]; + for (int i = 0; i < n; i++) + { + modelToView[modelIndex(i)] = i; + } + } + return modelToView; + } + + public int viewIndex(int modelIndex) + { + return getModelToView()[modelIndex]; + } + + // TableModel interface methods + + @Override + public int getRowCount() + { + return (tableModel == null) ? 0 : tableModel.getRowCount(); + } + + @Override + public int getColumnCount() + { + return (tableModel == null) ? 0 : tableModel.getColumnCount(); + } + + @Override + public String getColumnName(int column) + { + return tableModel.getColumnName(column); + } + + @Override + public Class getColumnClass(int column) + { + return tableModel.getColumnClass(column); + } + + @Override + public boolean isCellEditable(int row, int column) + { + return tableModel.isCellEditable(modelIndex(row), column); + } + + @Override + public Object getValueAt(int row, int column) + { + return tableModel.getValueAt(modelIndex(row), column); + } + + @Override + public void setValueAt(Object aValue, int row, int column) + { + tableModel.setValueAt(aValue, modelIndex(row), column); + } + + + public void setSortingEnabled(boolean aBool) + { + isSortEnabled = aBool; + + clearSortingState(); + } + + + + // Helper classes + + private class Row implements Comparable + { + private int modelIndex; + + public Row(int index) + { + this.modelIndex = index; + } + + @Override + public int compareTo(Object o) + { + int row1 = modelIndex; + int row2 = ((Row)o).modelIndex; + + for (Iterator it = sortingColumns.iterator(); it.hasNext();) + { + Directive directive = (Directive)it.next(); + int column = directive.column; + Object o1 = tableModel.getValueAt(row1, column); + Object o2 = tableModel.getValueAt(row2, column); + + int comparison = 0; + // Define null less than everything, except null. + if (o1 == null && o2 == null) + { + comparison = 0; + } + else if (o1 == null) + { + comparison = -1; + } + else if (o2 == null) + { + comparison = 1; + } + else + { + comparison = getComparator(column).compare(o1, o2); + } + if (comparison != 0) + { + return directive.direction == DESCENDING ? -comparison : comparison; + } + } + return 0; + } + } + + private class TableModelHandler implements TableModelListener + { + @Override + public void tableChanged(TableModelEvent e) + { + // If we're not sorting by anything, just pass the event along. + if (!isSorting()) + { + clearSortingState(); + fireTableChanged(e); + return; + } + + // If the table structure has changed, cancel the sorting; the + // sorting columns may have been either moved or deleted from + // the model. + if (e.getFirstRow() == TableModelEvent.HEADER_ROW) + { + cancelSorting(); + fireTableChanged(e); + return; + } + + // We can map a cell event through to the view without widening + // when the following conditions apply: + // + // a) all the changes are on one row (e.getFirstRow() == + // e.getLastRow()) and, + // b) all the changes are in one column (column != + // TableModelEvent.ALL_COLUMNS) and, + // c) we are not sorting on that column (getSortingStatus(column) == + // NOT_SORTED) and, + // d) a reverse lookup will not trigger a sort (modelToView != null) + // + // Note: INSERT and DELETE events fail this test as they have column == + // ALL_COLUMNS. + // + // The last check, for (modelToView != null) is to see if modelToView + // is already allocated. If we don't do this check; sorting can become + // a performance bottleneck for applications where cells + // change rapidly in different parts of the table. If cells + // change alternately in the sorting column and then outside of + // it this class can end up re-sorting on alternate cell updates - + // which can be a performance problem for large tables. The last + // clause avoids this problem. + int column = e.getColumn(); + if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED && modelToView != null) + { + int viewIndex = getModelToView()[e.getFirstRow()]; + fireTableChanged(new TableModelEvent(TableSorter.this, viewIndex, viewIndex, column, e.getType())); + return; + } + + // Something has happened to the data that may have invalidated the row + // order. + clearSortingState(); + fireTableDataChanged(); + return; + } + } + + private class MouseHandler extends MouseAdapter + { + @Override + public void mouseClicked(MouseEvent e) + { + JTableHeader h = (JTableHeader)e.getSource(); + TableColumnModel columnModel = h.getColumnModel(); + int viewColumn = columnModel.getColumnIndexAtX(e.getX()); + int column = columnModel.getColumn(viewColumn).getModelIndex(); + + // Bail if sorting is disabled + if (isSortEnabled == false) + return; + + if (column != -1) + { + int status = getSortingStatus(column); + if (!e.isControlDown()) + { + cancelSorting(); + } + + // // Cycle the sorting states through {NOT_SORTED, ASCENDING, + // DESCENDING} or + // // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift + // is pressed. + // status = status + (e.isShiftDown() ? -1 : 1); + // status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, + // 1} + if (status == ASCENDING) + status = DESCENDING; + else + status = ASCENDING; + setSortingStatus(column, status); + } + } + } + + private static class Arrow implements Icon + { + private boolean descending; + private int size; + private int priority; + + public Arrow(boolean descending, int size, int priority) + { + this.descending = descending; + this.size = size; + this.priority = priority; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) + { + Color color = c == null ? Color.GRAY : c.getBackground(); + // In a compound sort, make each succesive triangle 20% + // smaller than the previous one. + int dx = (int)(size / 2 * Math.pow(0.8, priority)); + int dy = descending ? dx : -dx; + // Align icon (roughly) with font baseline. + y = y + 5 * size / 6 + (descending ? -dy : 0); + int shift = descending ? 1 : -1; + g.translate(x, y); + + // Right diagonal. + g.setColor(color.darker()); + g.drawLine(dx / 2, dy, 0, 0); + g.drawLine(dx / 2, dy + shift, 0, shift); + + // Left diagonal. + g.setColor(color.brighter()); + g.drawLine(dx / 2, dy, dx, 0); + g.drawLine(dx / 2, dy + shift, dx, shift); + + // Horizontal line. + if (descending) + { + g.setColor(color.darker().darker()); + } + else + { + g.setColor(color.brighter().brighter()); + } + g.drawLine(dx, 0, 0, 0); + + g.setColor(color); + g.translate(-x, -y); + } + + @Override + public int getIconWidth() + { + return size; + } + + @Override + public int getIconHeight() + { + return size; + } + } + + private class SortableHeaderRenderer implements TableCellRenderer + { + private TableCellRenderer tableCellRenderer; + + public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) + { + this.tableCellRenderer = tableCellRenderer; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (c instanceof JLabel) + { + JLabel l = (JLabel)c; + l.setHorizontalTextPosition(JLabel.LEFT); + int modelColumn = table.convertColumnIndexToModel(column); + l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize())); + } + return c; + } + + /* + * public void setEnabled(boolean aBool) { + * getTableCellRendererComponent.setEnabled(aBool); + * + * } + */ + } + + private static class Directive + { + private int column; + private int direction; + + public Directive(int column, int direction) + { + this.column = column; + this.direction = direction; + } + } +} diff --git a/src/glum/gui/unit/DateUnitPanel.java b/src/glum/gui/unit/DateUnitPanel.java new file mode 100644 index 0000000..d88e756 --- /dev/null +++ b/src/glum/gui/unit/DateUnitPanel.java @@ -0,0 +1,306 @@ +package glum.gui.unit; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.TimeZone; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; + +import com.google.common.collect.Lists; + +import glum.database.QueryItem; +import glum.gui.GuiUtil; +import glum.gui.component.GComboBox; +import glum.gui.component.GLabel; +import glum.gui.component.GTextField; +import glum.gui.document.CharDocument; +import glum.gui.panel.itemList.ItemHandler; +import glum.gui.panel.itemList.ItemListPanel; +import glum.gui.panel.itemList.StaticItemProcessor; +import glum.gui.panel.itemList.query.QueryComposer; +import glum.gui.panel.itemList.query.QueryItemHandler; +import glum.unit.DateUnit; +import glum.unit.DateUnitProvider; +import glum.unit.UnitProvider; + +import net.miginfocom.swing.MigLayout; + +public class DateUnitPanel extends EditorPanel implements ActionListener +{ + // Constants + // @formatter:off + public static final String[][] DescriptionArr = { + {"G", "Era", "AD"}, + {"y", "Year", "1996"}, + {"M", "Month in year", "Jul; 07"}, + {"w", "Week in year", "27"}, + {"W", "Week in month", "2"}, + {"D", "Day in year", "189"}, + {"d", "Day in month", "10"}, + {"E", "Day in week", "Tue"}, + {"a", "AM/PM marker", "PM"}, + {"H", "Hour in day", "0"}, + {"h", "Hour in am/pm", "12"}, + {"m", "Minute in hour", "30"}, + {"s", "Second in minute", "55"}, + {"S", "Millisecond", "978"}, + {"z", "Time Zone: General", "Eastern; EST"}, + {"Z", "Time Zone: RFC 822", "-800"}}; + // @formatter:on + + // Gui components + private JRadioButton custRB, typeRB; + private GComboBox protoBox; + private GTextField custTF; + private GLabel exampleL; + private ItemListPanel ruleLP; + private DefaultTableCellRenderer plainRenderer; + private JLabel timeZoneL; + private GComboBox timeZoneBox; + + // State vars + private DateUnitProvider myUnitProvider; + + public DateUnitPanel() + { + super(); + + myUnitProvider = null; + + buildGuiArea(); + updateGui(); + } + + /** + * Sets in the available TimeZones the user can select + */ + public void setAvailableTimeZones(List itemList) + { + timeZoneBox.removeAllItems(); + for (TimeZone aTimeZone : itemList) + timeZoneBox.addItem(aTimeZone); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + updateModel(); + updateGui(); + + notifyListeners(this, ID_UPDATE, "unit.update"); + } + + @Override + public void setUnitProvider(UnitProvider aUnitProvider) + { + List protoNameList; + DateUnit activeUnit; + TimeZone activeTimeZone; + + // Update our UnitProvider + myUnitProvider = null; + if (aUnitProvider instanceof DateUnitProvider) + myUnitProvider = (DateUnitProvider)aUnitProvider; + + // Sync the GUI to the state of the aEditable + protoNameList = myUnitProvider.getProtoNameList(); + + activeUnit = myUnitProvider.getUnit(); + activeTimeZone = activeUnit.getTimeZone(); + + // Synch the GUI with the UnitProvider + custTF.setValue(myUnitProvider.getCustomPattern()); + + protoBox.removeAllItems(); + for (String aName : protoNameList) + protoBox.addItem(aName); + + protoBox.setChosenItem(myUnitProvider.getProtoUnit().getConfigName()); + + if (myUnitProvider.getIsCustom() == true) + custRB.setSelected(true); + else + typeRB.setSelected(true); + + for (TimeZone aTimeZone : timeZoneBox.getAllItems()) + { + if (activeTimeZone != null && aTimeZone.getID() == activeTimeZone.getID()) + { + timeZoneBox.setChosenItem(aTimeZone); + break; + } + } + + updateGui(); + } + + /** + * Forms the GUI + */ + private void buildGuiArea() + { + CharDocument charDoc; + QueryComposer aComposer; + ItemHandler itemHandler; + StaticItemProcessor itemProcessor; + int targH; + + setLayout(new MigLayout("", "0[left][grow]0", "0[][][][][][]0[grow]0")); + + // Example area + exampleL = new GLabel(null); + exampleL.setHorizontalAlignment(GLabel.CENTER); + add("growx,h 18!,span,wrap", exampleL); + + add(GuiUtil.createDivider(), "growx,h 4!,span,wrap"); + + // Specification area + typeRB = GuiUtil.createJRadioButton("Named:", this); + protoBox = new GComboBox(this); + add("span 1", typeRB); + add("growx,span,wrap", protoBox); + + // Custom area + custRB = GuiUtil.createJRadioButton("Custom:", this); + custTF = new GTextField(this); + charDoc = new CharDocument(custTF, "GyMwWDdEaHhmsSzZ :|\\/-,[](){}<>;.", true); + custTF.setDocument(charDoc); + add("span 1", custRB); + add("growx,span,wrap", custTF); + + // TimeZone area + timeZoneL = new JLabel("TimeZone:"); + timeZoneBox = new GComboBox(this, new TimeZoneCellRenderer()); + timeZoneBox.addItem(TimeZone.getDefault()); + for (String aID : TimeZone.getAvailableIDs()) + timeZoneBox.addItem(TimeZone.getTimeZone(aID)); + add("span 1", timeZoneL); + add("growx,span,w 0:100:,wrap", timeZoneBox); + + // Link the radio buttons + GuiUtil.linkRadioButtons(custRB, typeRB); + + // Rules table + List itemList; + itemList = Lists.newLinkedList(); + for (String[] aRow : DescriptionArr) + itemList.add(new PlainRow(aRow[0], aRow[1], aRow[2])); + + aComposer = new QueryComposer(); + aComposer.addAttribute(Lookup.Key, String.class, "Key", "Key"); + aComposer.addAttribute(Lookup.Comp, String.class, "Date Comp.", "Time Zone: General"); + aComposer.addAttribute(Lookup.Example, String.class, "Example", "Eastern; EST"); + aComposer.getItem(Lookup.Example).maxSize = 5000; + plainRenderer = new DefaultTableCellRenderer(); + for (Lookup aEnum : Lookup.values()) + aComposer.setRenderer(aEnum, plainRenderer); + + itemHandler = new QueryItemHandler(aComposer); + itemProcessor = new StaticItemProcessor(itemList); + + targH = ((custTF.getPreferredSize().height - 2) * DescriptionArr.length) + 2; + ruleLP = new ItemListPanel(itemHandler, itemProcessor, false, false); + ruleLP.setPreferredSize(new Dimension(ruleLP.getPreferredSize().width, targH)); + ruleLP.setSortingEnabled(false); + ruleLP.setEnabled(false); + add("growx,h 70::,span", ruleLP); + } + + /** + * Updates the UnitProvider to be synch with the GUI + */ + private void updateModel() + { + TimeZone timeZone; + + // Need a valid UnitProvider + if (myUnitProvider == null) + return; + + timeZone = timeZoneBox.getChosenItem(); + if (typeRB.isSelected() == true) + myUnitProvider.activateProto(timeZone, protoBox.getChosenItem()); + else + myUnitProvider.activateCustom(timeZone, custTF.getText()); + } + + /** + * Updates the GUI + */ + private void updateGui() + { + DateUnit activeUnit; + String exampleStr; + boolean isEnabled; + long currTime; + + isEnabled = custRB.isSelected(); + custTF.setEnabled(isEnabled); + protoBox.setEnabled(!isEnabled); +// itemLP.repaint(); + + // Retrieve the activeUnit + activeUnit = null; + if (myUnitProvider != null) + activeUnit = myUnitProvider.getUnit(); + + // Update the example area + currTime = System.currentTimeMillis(); + exampleStr = ""; + if (activeUnit != null) + exampleStr = activeUnit.getString(currTime); + + exampleL.setValue(exampleStr); + } + + /** + * Helper classes to aide with setting up the info table + */ + enum Lookup + { + Key, Comp, Example + }; + + class PlainRow implements QueryItem + { + private String key, comp, example; + + public PlainRow(String aKey, String aComp, String aExample) + { + key = aKey; + comp = aComp; + example = aExample; + } + + @Override + public Object getValue(Lookup aEnum) + { + switch (aEnum) + { + case Key: + return key; + + case Comp: + return comp; + + case Example: + return example; + + default: + return null; + } + + } + + @Override + public void setValue(Lookup aEnum, Object aObj) + { + throw new UnsupportedOperationException(); + + } + } + +} diff --git a/src/glum/gui/unit/DecimalUnitPanel.java b/src/glum/gui/unit/DecimalUnitPanel.java new file mode 100644 index 0000000..af93d62 --- /dev/null +++ b/src/glum/gui/unit/DecimalUnitPanel.java @@ -0,0 +1,158 @@ +package glum.gui.unit; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.DecimalFormat; +import java.util.List; +import javax.swing.*; + +import glum.gui.GuiUtil; +import glum.gui.component.GComboBox; +import glum.gui.component.GNumberField; +import glum.unit.ConstUnitProvider; +import glum.unit.DecimalUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.Unit; +import glum.unit.UnitProvider; + +import net.miginfocom.swing.MigLayout; + +public class DecimalUnitPanel extends EditorPanel implements ActionListener +{ + // Gui components + private GComboBox unitBox; + private GNumberField decimalPlacesNF; + private JCheckBox forceLongUnitsCB; + private JLabel unitL, decimalPlacesL; + + // State vars + private DecimalUnitProvider myUnitProvider; + + public DecimalUnitPanel() + { + super(); + + myUnitProvider = null; + + buildGuiArea(); + updateGui(); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + updateGui(); + updateModel(); + + notifyListeners(this, ID_UPDATE, "unit.update"); + } + + @Override + public void setUnitProvider(UnitProvider aUnitProvider) + { + int decimalPlaces; + boolean forceFullLabel; + List unitList; + Unit protoUnit, chosenUnit; + + // Update our UnitProvider + myUnitProvider = null; + if (aUnitProvider instanceof DecimalUnitProvider) + myUnitProvider = (DecimalUnitProvider)aUnitProvider; + + // Sync the GUI to the state of the aEditable + decimalPlaces = 0; + forceFullLabel = false; + unitList = null; + protoUnit = null; + if (myUnitProvider != null) + { + decimalPlaces = myUnitProvider.getDecimalPlaces(); + forceFullLabel = myUnitProvider.getForceFullLabel(); + unitList = myUnitProvider.getProtoUnitList(); + protoUnit = myUnitProvider.getProtoUnit(); + } + + // Synch the GUI with the UnitProvider + decimalPlacesNF.setValue(decimalPlaces); + forceLongUnitsCB.setSelected(forceFullLabel); + + chosenUnit = null; + unitBox.removeAllItems(); + for (Unit aUnit : unitList) + { + unitBox.addItem(aUnit); + if (aUnit == protoUnit) + chosenUnit = aUnit; + } + + unitBox.removeActionListener(this); + unitBox.setSelectedItem(chosenUnit); + unitBox.addActionListener(this); + updateGui(); + } + + /** + * Forms the GUI + */ + private void buildGuiArea() + { + UnitProvider countUP; + + setLayout(new MigLayout("", "0[right][][grow]0", "0[][]0[]0")); + countUP = new ConstUnitProvider(new NumberUnit("", "", 1.0, new DecimalFormat("###,###,###,###,##0"))); + + // Unit area + unitL = new JLabel("Unit:"); + unitBox = new GComboBox(this, new UnitLabelRenderer()); + add("span 1", unitL); + add("growx,span,wrap", unitBox); + + // DecimalPlaces area + decimalPlacesL = new JLabel("Decimal Places:"); + decimalPlacesNF = new GNumberField(this, countUP, 0, 9); + add("span 2", decimalPlacesL); + add("growx,span 1,wrap", decimalPlacesNF); + + // ForceFullLabel + forceLongUnitsCB = GuiUtil.createJCheckBox("Force long units", this); + add("align left,growx,span", forceLongUnitsCB); + } + + /** + * Updates the gui + */ + private void updateGui() + { + Unit protoUnit; + boolean isEnabled; + + // Need a valid UnitProvider + if (myUnitProvider == null) + return; + + // Synch the gui components + protoUnit = unitBox.getChosenItem(); + isEnabled = (protoUnit instanceof NumberUnit); + forceLongUnitsCB.setEnabled(isEnabled); + } + + /** + * Updates the associated UnitProvider + */ + private void updateModel() + { + Unit protoUnit; + int decimalPlaces; + boolean forceLongUnits; + + // Get the gui configuration + protoUnit = unitBox.getChosenItem(); + decimalPlaces = decimalPlacesNF.getValueAsInt(0); + forceLongUnits = forceLongUnitsCB.isSelected(); + + // Update the UnitProvider + myUnitProvider.activate(protoUnit, decimalPlaces, forceLongUnits); + } + +} diff --git a/src/glum/gui/unit/EditorPanel.java b/src/glum/gui/unit/EditorPanel.java new file mode 100644 index 0000000..75abeea --- /dev/null +++ b/src/glum/gui/unit/EditorPanel.java @@ -0,0 +1,13 @@ +package glum.gui.unit; + +import glum.gui.panel.GPanel; +import glum.unit.UnitProvider; + +public class EditorPanel extends GPanel +{ + /** + * Sets in the UnitProvider that is to be configured + */ + public void setUnitProvider(UnitProvider aProvider) { ; } + +} diff --git a/src/glum/gui/unit/LatLonUnitPanel.java b/src/glum/gui/unit/LatLonUnitPanel.java new file mode 100644 index 0000000..4f933d1 --- /dev/null +++ b/src/glum/gui/unit/LatLonUnitPanel.java @@ -0,0 +1,196 @@ +package glum.gui.unit; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.DecimalFormat; +import javax.swing.*; + +import net.miginfocom.swing.MigLayout; + +import glum.gui.GuiUtil; +import glum.gui.component.GComboBox; +import glum.gui.component.GNumberField; +import glum.gui.panel.CardPanel; +import glum.unit.ConstUnitProvider; +import glum.unit.LatLonUnitProvider; +import glum.unit.NumberUnit; +import glum.unit.UnitProvider; + +public class LatLonUnitPanel extends EditorPanel implements ActionListener +{ + // Gui components + private GComboBox unitBox; + private CardPanel editPanel; + private GNumberField decimalPlacesNF; + private JCheckBox isZeroCenteredCB; + private JCheckBox isSecondsShownCB; + + // State vars + private LatLonUnitProvider myUnitProvider; + + public LatLonUnitPanel() + { + super(); + + myUnitProvider = null; + buildGuiArea(); + updateGui(); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == unitBox) + updateGui(); + + updateEditable(); + notifyListeners(this, ID_UPDATE, "unit.update"); + } + + @Override + public void setUnitProvider(UnitProvider aUnitProvider) + { + String unitTypeStr; + int decimalPlaces; + boolean isSecondsShown; + boolean isZeroCentered; + + // Update our UnitProvider + myUnitProvider = null; + if (aUnitProvider instanceof LatLonUnitProvider) + myUnitProvider = (LatLonUnitProvider)aUnitProvider; + + // Sync the GUI to the state of the UnitProvider + decimalPlaces = decimalPlacesNF.getValueAsInt(0); + isSecondsShown = isSecondsShownCB.isSelected(); + isZeroCentered = isZeroCenteredCB.isSelected(); + unitTypeStr = "Raw"; + if (myUnitProvider != null) + { + if (myUnitProvider.isRawUnitActive() == true) + { + decimalPlaces = myUnitProvider.getDecimalPlaces(); + isZeroCentered = myUnitProvider.isZeroCentered(); + unitTypeStr = "Raw"; + } + else + { + isSecondsShown = myUnitProvider.isSecondsShown(); + unitTypeStr = "Standard"; + } + } + + // Synch the GUI with the UnitProvider + decimalPlacesNF.setValue(decimalPlaces); + isSecondsShownCB.setSelected(isSecondsShown); + isZeroCenteredCB.setSelected(isZeroCentered); + + unitBox.removeActionListener(this); + unitBox.setSelectedItem(unitTypeStr); + unitBox.addActionListener(this); + + updateGui(); + } + + /** + * Forms the GUI + */ + private void buildGuiArea() + { + JLabel unitL; + + setLayout(new MigLayout("", "0[right][][grow]0", "0[][grow]0")); + + // Unit area + unitL = new JLabel("Unit:", JLabel.CENTER); + unitBox = new GComboBox(this, "Raw", "Standard"); + add("span 1", unitL); + add("growx,span,wrap", unitBox); + + // Edit area + editPanel = new CardPanel(); + editPanel.addCard("Raw", formRawPanel()); + editPanel.addCard("Standard", formStandardPanel()); + add("growx,growy,span", editPanel); + } + + /** + * Forms the panel to configure the raw unit for lat/lon + */ + private JPanel formRawPanel() + { + JPanel tmpPanel; + JLabel tmpL; + UnitProvider countUP; + + tmpPanel = new JPanel(new MigLayout("", "0[right][][grow]0", "0[]0")); + countUP = new ConstUnitProvider(new NumberUnit("", "", 1.0, new DecimalFormat("###,###,###,###,##0"))); + + // DecimalPlaces area + tmpL = new JLabel("Decimal Places:", JLabel.CENTER); + decimalPlacesNF = new GNumberField(this, countUP, 0, 9); + tmpPanel.add("span 2", tmpL); + tmpPanel.add("growx,span 1,wrap", decimalPlacesNF); + + // Zero centered area + isZeroCenteredCB = GuiUtil.createJCheckBox("Zero Centered", this); + tmpPanel.add("align left,span", isZeroCenteredCB); + + return tmpPanel; + } + + /** + * Forms the panel to configure the standard unit for lat/lon + */ + private JPanel formStandardPanel() + { + JPanel tmpPanel; + + tmpPanel = new JPanel(new MigLayout("", "0[grow]0", "0[]0")); + + isSecondsShownCB = GuiUtil.createJCheckBox("Show Seconds", this); + tmpPanel.add("span 1", isSecondsShownCB); + + return tmpPanel; + } + + /** + * Updates the GUI + */ + private void updateGui() + { + String unitTypeStr; + + unitTypeStr = unitBox.getChosenItem(); + editPanel.switchToCard(unitTypeStr); + } + + /** + * Updates the UnitProvider + */ + private void updateEditable() + { + String unitTypeStr; + boolean isSecondsShown; + boolean isZeroCentered; + int decimalPlaces; + + // Bail if no UnitProvider + if (myUnitProvider == null) + return; + + isSecondsShown = isSecondsShownCB.isSelected(); + isZeroCentered = isZeroCenteredCB.isSelected(); + decimalPlaces = decimalPlacesNF.getValueAsInt(0); + + unitTypeStr = unitBox.getChosenItem(); + if (unitTypeStr.equals("Raw") == true) + myUnitProvider.activateRaw(decimalPlaces, isZeroCentered); + else + myUnitProvider.activateStandard(isSecondsShown); + } + +} diff --git a/src/glum/gui/unit/TimeZoneCellRenderer.java b/src/glum/gui/unit/TimeZoneCellRenderer.java new file mode 100644 index 0000000..fcd7837 --- /dev/null +++ b/src/glum/gui/unit/TimeZoneCellRenderer.java @@ -0,0 +1,33 @@ +package glum.gui.unit; + +import java.awt.Component; +import java.util.TimeZone; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JLabel; +import javax.swing.JList; + +public class TimeZoneCellRenderer extends DefaultListCellRenderer +{ + public TimeZoneCellRenderer() + { + super(); + } + + @Override + public Component getListCellRendererComponent(JList list, Object aObj, int index, boolean isSelected, boolean hasFocus) + { + JLabel retL; + String aStr; + + retL = (JLabel)super.getListCellRendererComponent(list, aObj, index, isSelected, hasFocus); + if (aObj instanceof TimeZone) + { + aStr = ((TimeZone)aObj).getID(); + retL.setText(aStr); + } + + return retL; + } + +} diff --git a/src/glum/gui/unit/UnitConfigurationDialog.java b/src/glum/gui/unit/UnitConfigurationDialog.java new file mode 100644 index 0000000..7d5e698 --- /dev/null +++ b/src/glum/gui/unit/UnitConfigurationDialog.java @@ -0,0 +1,303 @@ +package glum.gui.unit; + +import java.awt.*; +import java.awt.event.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.table.*; + +import net.miginfocom.swing.MigLayout; + +import glum.gui.FocusUtil; +import glum.gui.GuiUtil; +import glum.gui.action.ClickAction; +import glum.gui.panel.CardPanel; +import glum.gui.panel.itemList.BasicItemHandler; +import glum.gui.panel.itemList.ItemHandler; +import glum.gui.panel.itemList.ItemListPanel; +import glum.gui.panel.itemList.StaticItemProcessor; +import glum.gui.panel.itemList.query.QueryComposer; +import glum.reflect.FunctionRunnable; +import glum.unit.UnitListener; +import glum.unit.UnitProvider; + +public class UnitConfigurationDialog extends JDialog implements ActionListener, ListSelectionListener, UnitListener +{ + // Gui Components + private ItemListPanel itemLP; + private CardPanel editorPanel; + private JLabel titleL; + private JButton closeB; + + // State vars + private StaticItemProcessor itemProcessor; + + public UnitConfigurationDialog(JFrame parentFrame) + { + // Make sure we call the parent + super(parentFrame); + + // Set the characteristics for this dialog + setTitle("Edit Unit"); + + setDefaultCloseOperation(HIDE_ON_CLOSE); + setLocationRelativeTo(parentFrame); + setModal(false); + + // Build the actual GUI + buildGuiArea(); + updateGui(); + pack(); + + // Set up keyboard short cuts + FocusUtil.addWindowKeyBinding(getRootPane(), "ESCAPE", new ClickAction(closeB)); + FocusUtil.addWindowKeyBinding(getRootPane(), "ENTER", new ClickAction(closeB)); +// FocusUtil.addFocusKeyBinding(itemLP.getTable(), "ENTER", new ClickAction(closeB)); + } + + /** + * Adds an EditorPanel and associateds with aClass + */ + public void addEditorPanel(Class aClass, EditorPanel aPanel) + { + Dimension aDim; + + // Insanity check + if (aClass == null || aPanel == null) + return; + + // Add the editorPanel and associate with the specified class + editorPanel.addCard("" + aClass, aPanel); + editorPanel.switchToCard("null"); + updateGui(); + + aDim = editorPanel.getMinimumSize(); + for (EditorPanel evalPanel : editorPanel.getAllCards()) + { + if (evalPanel.getMinimumSize().height > aDim.height) + aDim.height = evalPanel.getMinimumSize().height; + } + + // Set the dialog to the best initial size + setSize(getPreferredSize().width, itemLP.getHeight() + closeB.getHeight() + titleL.getHeight() + aDim.height); + } + + /** + * Adds a UnitProvider to our set of UnitProviders + */ + public void addUnitProvider(UnitProvider aUnitProvider) + { + List itemList; + + // Insanity check + if (aUnitProvider == null) + return; + + // Register for state changes on the UnitProvider + aUnitProvider.addListener(this); + + // Update the table processor + itemList = itemProcessor.getItems(); + itemList.add(aUnitProvider); + ; + itemProcessor.setItems(itemList); + + // Update the dialog + updateTable(); + } + + @Override + public void actionPerformed(ActionEvent e) + { + Object source; + + source = e.getSource(); + if (source == closeB) + { + setVisible(false); + } + } + + @Override + public void unitChanged(UnitProvider aProvider, String aKey) + { + itemLP.repaint(); + } + + @Override + public void valueChanged(ListSelectionEvent aEvent) + { + Object source; + + source = aEvent.getSource(); + if (source == itemLP) + { + if (aEvent.getValueIsAdjusting() == true) + return; + + updateGui(); + } + } + + /** + * Form the actual GUI + */ + private void buildGuiArea() + { + QueryComposer aComposer; + ItemHandler itemHandler; + EditorPanel nullPanel; + + setLayout(new MigLayout("", "[center,grow]", "2[]3[grow][fill,growprio 200][]")); + + // Build the title label + titleL = new JLabel("Units", JLabel.CENTER); + add("span,wrap", titleL); + + // UnitProvider table + aComposer = new QueryComposer(); + aComposer.addAttribute(Lookup.Key, String.class, "Type", null); + aComposer.addAttribute(Lookup.Value, String.class, "Value", null); + + itemHandler = new UnitProviderHandler(aComposer); + itemProcessor = new StaticItemProcessor(); + + itemLP = new ItemListPanel(itemHandler, itemProcessor, false, false); + itemLP.addListSelectionListener(this); + itemLP.setSortingEnabled(false); + add("growx,growy,h 70::,span,wrap", itemLP); + + // Form the editor area + nullPanel = new EditorPanel(); + nullPanel.setPreferredSize(new Dimension(1, 1)); + + editorPanel = new CardPanel(); + editorPanel.addCard("null", nullPanel); + add("growx,growy,wrap", editorPanel); + + // Action area + closeB = GuiUtil.createJButton("Close", this); + add("align right", closeB); + } + + /** + * updateGui + */ + private void updateGui() + { + EditorPanel aPanel; + UnitProvider aUnitProvider; + Dimension aDim; + String cardName; + + // Get the selected UnitProvider + aUnitProvider = itemLP.getSelectedItem(); + + // Switch to the appropriate Editor + cardName = "null"; + if (aUnitProvider != null) + cardName = "" + aUnitProvider.getClass(); + editorPanel.switchToCard(cardName); + + // Resize the editorPanel to be as compact as the active card + aPanel = editorPanel.getActiveCard(); + aPanel.setUnitProvider(aUnitProvider); + aDim = aPanel.getPreferredSize(); +// System.out.println("minHeight: " + aDim.getHeight() + " hmm: " + aPanel); + + editorPanel.setMaximumSize(new Dimension(5000, aDim.height)); + // Hack to get the editorPanel resize properly. Not sure why invalidate(), validate() do not work + int aHeight = getHeight(); + SwingUtilities.invokeLater(new FunctionRunnable(this, "setSize", getWidth(), aHeight - 1)); + SwingUtilities.invokeLater(new FunctionRunnable(this, "setSize", getWidth(), aHeight)); +// SwingUtilities.invokeLater(new FunctionRunnable(editorPanel, "invalidate")); +// SwingUtilities.invokeLater(new FunctionRunnable(editorPanel, "validate")); +// invalidate(); +// validate(); + } + + /** + * updateTable + */ + private void updateTable() + { + List itemList; + JTableHeader aTableHeader; + TableColumnModel aTableColumnModel; + TableColumn aTableColumn; + int aWidth, tmpWidth; + JLabel aLabel; + + // Update myTable column[0] width + aWidth = 10; + aLabel = new JLabel(""); + itemList = itemProcessor.getItems(); + for (UnitProvider aUnitProvider : itemList) + { + aLabel.setText(aUnitProvider.getDisplayName()); + tmpWidth = aLabel.getPreferredSize().width + 5; + if (aWidth < tmpWidth) + aWidth = tmpWidth; + } + + // Set sizing attributes of the column 1 + aTableHeader = itemLP.getTable().getTableHeader(); + aTableColumnModel = aTableHeader.getColumnModel(); + aTableColumn = aTableColumnModel.getColumn(0); + aTableColumn.setResizable(false); + aTableColumn.setMinWidth(aWidth); + aTableColumn.setMaxWidth(aWidth); + aTableColumn.setPreferredWidth(aWidth); + } + + /** + * Helper classes to aide with setting up the UnitProvider table + */ + enum Lookup + { + Key, Value, + }; + + public class UnitProviderHandler extends BasicItemHandler + { + public UnitProviderHandler(QueryComposer aComposer) + { + super(aComposer); + } + + @Override + public Object getColumnValue(UnitProvider aItem, int colNum) + { + Enum refKey; + + // Insanity check + if (colNum < 0 && colNum >= fullAttributeList.size()) + return null; + + refKey = fullAttributeList.get(colNum).refKey; + switch ((Lookup)refKey) + { + case Key: + return aItem.getDisplayName(); + + case Value: + return aItem.getConfigName(); + + default: + break; + } + + return null; + } + + @Override + public void setColumnValue(UnitProvider aItem, int colNum, Object aValue) + { + throw new RuntimeException("Unsupported Operation."); + } + + } + +} diff --git a/src/glum/gui/unit/UnitLabelRenderer.java b/src/glum/gui/unit/UnitLabelRenderer.java new file mode 100644 index 0000000..83eca8e --- /dev/null +++ b/src/glum/gui/unit/UnitLabelRenderer.java @@ -0,0 +1,31 @@ +package glum.gui.unit; + +import java.awt.Component; +import javax.swing.*; + +import glum.unit.Unit; + +public class UnitLabelRenderer extends DefaultListCellRenderer +{ + public UnitLabelRenderer() + { + super(); + } + + @Override + public Component getListCellRendererComponent(JList list, Object aObj, int index, boolean isSelected, boolean hasFocus) + { + JLabel retL; + String aStr; + + retL = (JLabel)super.getListCellRendererComponent(list, aObj, index, isSelected, hasFocus); + if (aObj instanceof Unit) + { + aStr = ((Unit)aObj).getConfigName(); + retL.setText(aStr); + } + + return retL; + } + +} diff --git a/src/glum/io/ConfigMapTP.java b/src/glum/io/ConfigMapTP.java new file mode 100644 index 0000000..1091c9b --- /dev/null +++ b/src/glum/io/ConfigMapTP.java @@ -0,0 +1,41 @@ +package glum.io; + +import glum.registry.ConfigMap; + +public class ConfigMapTP implements TokenProcessor +{ + // State vars + ConfigMap refConfigMap; + + public ConfigMapTP(ConfigMap aConfigMap) + { + refConfigMap = aConfigMap; + } + + @Override + public void flush() + { + ; // Nothing to do + } + + @Override + public boolean process(String[] tokens, int lineNum) + { + // Insanity check + if (tokens == null || tokens.length <= 1) + return false; + + if (tokens.length == 2) + { + refConfigMap.put(tokens[0], tokens[1]); + } + else + { + for (int c1 = 1; c1 < tokens.length; c1++) + refConfigMap.addItem(tokens[0], tokens[c1]); + } + + return true; + } + +} diff --git a/src/glum/io/IoUtil.java b/src/glum/io/IoUtil.java new file mode 100644 index 0000000..6d93b62 --- /dev/null +++ b/src/glum/io/IoUtil.java @@ -0,0 +1,422 @@ +package glum.io; + +import glum.task.*; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.*; +import java.net.*; +import java.nio.channels.Channel; +import java.util.Map; + +import javax.xml.bind.DatatypeConverter; + +import com.google.common.collect.Maps; + +public class IoUtil +{ + /** + * Creates a URL without throwing an exception. Returns null on failure + */ + public static URL createURL(String aUrlStr) + { + URL aURL; + + try + { + aURL = new URL(aUrlStr); + } + catch (MalformedURLException e) + { + aURL = null; + } + + return aURL; + } + + /** + * Prints out the error msg followed by the stack trace. + */ + public static void dumpTrace(Exception aExp, String aMsg) + { + System.out.println(aMsg); + aExp.printStackTrace(); + } + + /** + * Forces aChannel to be closed (silently) + */ + public static void forceClose(Channel aChannel) + { + if (aChannel == null) + return; + + try + { + aChannel.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Forces an InputStream to be closed (silently) + */ + public static void forceClose(InputStream aStream) + { + if (aStream == null) + return; + + try + { + aStream.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Forces an OutputStream to be closed (silently) + */ + public static void forceClose(OutputStream aStream) + { + if (aStream == null) + return; + + try + { + aStream.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Forces a Reader to be closed (silently) + */ + public static void forceClose(Reader aReader) + { + if (aReader == null) + return; + + try + { + aReader.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Forces a Writer to be closed (silently) + */ + public static void forceClose(Writer aWriter) + { + if (aWriter == null) + return; + + try + { + aWriter.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + + } + + /** + * Forces a ZinStream to be closed (silently) + */ + public static void forceClose(ZinStream aStream) + { + if (aStream == null) + return; + + try + { + aStream.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Forces a ZoutStream to be closed (silently) + */ + public static void forceClose(ZoutStream aStream) + { + if (aStream == null) + return; + + try + { + aStream.close(); + } + catch (Exception aExp) + { + aExp.printStackTrace(); + } + } + + /** + * Downloads the content at aUrl and saves it to aFile. Before the file is downloaded, the connection is configured + * with the specified property map. The download will be aborted if aTask is no longer active. + */ + public static boolean copyUrlToFile(Task aTask, URL aUrl, File aFile, Map aPropertyMap) + { + URLConnection aConnection; + InputStream inStream; + OutputStream outStream; + byte[] byteArr; + int numBytes; + + // Ensure we have a valid aTask + if (aTask == null) + aTask = new ConsoleTask(); + + // Allocate space for the byte buffer + byteArr = new byte[10000]; + + // Perform the actual copying + inStream = null; + outStream = null; + try + { + // Open the src stream (with a 30 sec connect timeout) + aConnection = aUrl.openConnection(); + aConnection.setConnectTimeout(30 * 1000); + aConnection.setReadTimeout(90 * 1000); + + // Setup the various properties + if (aPropertyMap != null) + { + for (String aKey : aPropertyMap.keySet()) + aConnection.setRequestProperty(aKey, aPropertyMap.get(aKey)); + } + + inStream = aConnection.getInputStream(); +// inStream = aUrl.openStream(); + + // Open the dest stream + outStream = new FileOutputStream(aFile); + + // Copy the bytes from the instream to the outstream + numBytes = 0; + while (numBytes != -1) + { + numBytes = inStream.read(byteArr); + if (numBytes > 0) + outStream.write(byteArr, 0, numBytes); + + // Bail if aTask is aborted + if (aTask.isActive() == false) + { + aTask.infoAppendln("Download of file: " + aFile + " has been aborted!"); + return false; + } + } + } + catch (Exception aExp) + { + aTask.infoAppendln("Exception:" + aExp); + aTask.infoAppendln(" URL:" + aUrl); + aTask.infoAppendln(" File:" + aFile); + aExp.printStackTrace(); + return false; + } + finally + { + forceClose(inStream); + forceClose(outStream); + } + + return true; + } + + /** + * Downloads the content at aUrl and saves it to aFile. Before the file is downloaded, the connection is configured + * with the specified property map. + */ + public static boolean copyUrlToFile(URL aUrl, File aFile, Map aPropertyMap) + { + return copyUrlToFile(null, aUrl, aFile, aPropertyMap); + } + + /** + * Downloads the content at aUrl and saves it to aFile + */ + public static boolean copyUrlToFile(URL aUrl, File aFile) + { + return copyUrlToFile(aUrl, aFile, null); + } + + /** + * Downloads the content at aUrl and saves it to aFile. Before the file is downloaded, the connection is configured + * for basic authorization using the provided credentials (aUsername and aPassword). + */ + public static boolean copyUrlToFile(Task aTask, URL aUrl, File aFile, String aUsername, String aPassword) + { + Map plainMap; + String authStr; + + authStr = aUsername + ":" + aPassword; + authStr = DatatypeConverter.printBase64Binary(authStr.getBytes()); + + plainMap = Maps.newHashMap(); + plainMap.put("Authorization", "Basic " + authStr); + + return copyUrlToFile(aTask, aUrl, aFile, plainMap); + } + + /** + * Downloads the content at aUrl and saves it to aFile. Before the file is downloaded, the connection is configured + * for basic authorization using the provided credentials (aUsername and aPassword). + */ + public static boolean copyUrlToFile(URL aUrl, File aFile, String aUsername, String aPassword) + { + return copyUrlToFile(null, aUrl, aFile, aUsername, aPassword); + } + + /** + * Copies the content at aFile1 and saves it to aFile2 + */ + public static boolean copyFileToFile(File aFile1, File aFile2) + { + URL aUrl; + + try + { + aUrl = aFile1.toURI().toURL(); + } + catch (Exception aExp) + { + System.out.println("Exception:" + aExp); + aExp.printStackTrace(); + return false; + } + + return copyUrlToFile(aUrl, aFile2); + } + + /** + * Method to recursively delete all of the contents located in the specified directory. + *

    + * Source: http://stackoverflow.com/questions/3775694/deleting-folder-from-java + *

    + */ + public static boolean deleteDirectory(File directory) + { + if (directory.exists()) + { + File[] files = directory.listFiles(); + if (null != files) + { + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + deleteDirectory(files[i]); + } + else + { + files[i].delete(); + } + } + } + } + return (directory.delete()); + } + + /** + * Reads an 8-bit string from aStream. First 2 bytes specify length of string. + */ + public static String readString(DataInputStream aStream) throws IOException + { + byte[] data; + int size; + + size = aStream.readShort() & 0x00FFFF; + if (size == 0x00FFFF) + return null; + if (size == 0) + return ""; + + data = new byte[size]; + aStream.readFully(data); + return new String(data, "UTF-8"); + } + + /** + * Writes an 8-bit string to aStream. First 2 bytes specify length of string. + */ + public static void writeString(DataOutputStream aStream, String aStr) throws IOException + { + byte[] data; + int size; + + // Null strings are handled in special fashion + if (aStr == null) + { + aStream.writeShort(0x00FFFF); + return; + } + + // Empty strings are handled in special fashion + if (aStr.equals("") == true) + { + aStream.writeShort(0); + return; + } + + data = aStr.getBytes("UTF-8"); + size = data.length; + + // Ensure the string size is less than 0x00FFFF + if (size >= 0x00FFFF) + throw new RuntimeException("Transformed UTF-8 string is too large! Max size: " + (0x00FFFF - 1) + " Curr size:" + size); + + // Write out the string + aStream.writeShort(size & 0x00FFFF); + aStream.write(data); + } + + /** + * Calculates the raw space needed to store an 8-bit string. + */ + public static short sizeOnDisk(String aStr) + { + byte[] data; + int size; + + if (aStr == null || aStr.equals("") == true) + return 2; + + try + { + data = aStr.getBytes("UTF-8"); + size = data.length; + } + catch (Exception aExp) + { + throw new RuntimeException("UTF-8 Transform error.", aExp); + } + + if (size >= 0x00FFFF) + throw new RuntimeException("Transformed UTF-8 string is too large! Max size: " + (0x00FFFF - 1) + " Curr size:" + size); + + return (short)(2 + data.length); + } + +} diff --git a/src/glum/io/Loader.java b/src/glum/io/Loader.java new file mode 100644 index 0000000..0fa41c3 --- /dev/null +++ b/src/glum/io/Loader.java @@ -0,0 +1,392 @@ +package glum.io; + +import java.awt.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.List; +import javax.swing.*; +import javax.swing.filechooser.FileFilter; + +import com.google.common.collect.Lists; + +import glum.io.token.MatchTokenizer; +import glum.io.token.Tokenizer; +import glum.task.Task; + +public class Loader +{ + // Constants + // Default regEx handles the following cases: + // (1) 1-Liner comments started by the char: # + // (2) Any double quoted text + // (3) Any alphanumeric string including the following special symbols: [. / | : ] + public static final String DEFAULT_REG_EX = "(#.*$)|([-+]?[_a-zA-Z0-9\\.\\^\\-\\/\\|\\:]+)|(\"[^\"\\r\\n]*\")|([a-zA-Z0-9\\.\\^\\-\\/\\|]+)"; + + public static void loadAsciiFile(File aFile, TokenProcessor aTokenProcessor) + { + loadAsciiFile(aFile, aTokenProcessor, new MatchTokenizer(DEFAULT_REG_EX)); + } + + public static void loadAsciiFile(File aFile, TokenProcessor aTokenProcessor, Tokenizer aTokenizer) + { + Collection tpSet; + + tpSet = new LinkedList(); + tpSet.add(aTokenProcessor); + loadAsciiFile(aFile, tpSet, aTokenizer); + } + + public static void loadAsciiFile(File aFile, Collection tpSet) + { + loadAsciiFile(aFile, tpSet, new MatchTokenizer(DEFAULT_REG_EX)); + } + + public static void loadAsciiFile(File aFile, Collection tpSet, Tokenizer aTokenizer) + { + loadAsciiFile(aFile, tpSet, aTokenizer, null); + } + + public static void loadAsciiFile(File aFile, Collection tpSet, Tokenizer aTokenizer, Task aTask) + { + URL aUrl; + + try + { + aUrl = aFile.toURI().toURL(); + loadAsciiFile(aUrl, tpSet, aTokenizer, aTask); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("Resource not processed: " + aFile); + return; + } + } + + public static void loadAsciiFile(URL aUrl, TokenProcessor aTokenProcessor) + { + Collection tpSet; + + tpSet = new LinkedList(); + tpSet.add(aTokenProcessor); + loadAsciiFile(aUrl, tpSet, new MatchTokenizer(DEFAULT_REG_EX)); + } + + public static void loadAsciiFile(URL aUrl, Collection tpSet) + { + loadAsciiFile(aUrl, tpSet, new MatchTokenizer(DEFAULT_REG_EX)); + } + + public static void loadAsciiFile(URL aUrl, Collection tpSet, Tokenizer aTokenizer) + { + loadAsciiFile(aUrl, tpSet, aTokenizer, null); + } + + public static void loadAsciiFile(URL aUrl, Collection tpSet, Tokenizer aTokenizer, Task aTask) + { + InputStream inStream; + + // Insanity check + if (aUrl == null) + return; + + // Process our input + inStream = null; + try + { + inStream = aUrl.openStream(); + loadAsciiFile(inStream, tpSet, aTokenizer, aTask); + } + catch (FileNotFoundException e) + { + System.out.println("Resource not found: " + aUrl); + return; + } + catch (IOException e) + { + System.out.println("Ioexception occured while loading: " + aUrl); + return; + } + finally + { + IoUtil.forceClose(inStream); + } + } + + public static void loadAsciiFile(InputStream inStream, TokenProcessor aTokenProcessor) throws IOException + { + loadAsciiFile(inStream, aTokenProcessor, new MatchTokenizer(DEFAULT_REG_EX)); + } + + public static void loadAsciiFile(InputStream inStream, TokenProcessor aTokenProcessor, Tokenizer aTokenizer) throws IOException + { + loadAsciiFile(inStream, aTokenProcessor, aTokenizer, null); + } + + public static void loadAsciiFile(InputStream inStream, TokenProcessor aTokenProcessor, Tokenizer aTokenizer, Task aTask) throws IOException + { + Collection tpSet; + + tpSet = new LinkedList(); + tpSet.add(aTokenProcessor); + loadAsciiFile(inStream, tpSet, aTokenizer, aTask); + } + + public static void loadAsciiFile(InputStream inStream, Collection tpSet) throws IOException + { + loadAsciiFile(inStream, tpSet, new MatchTokenizer(DEFAULT_REG_EX), null); + } + + public static void loadAsciiFile(InputStream inStream, Collection tpSet, Tokenizer aTokenizer, Task aTask) throws IOException + { + BufferedReader br; + String strLine; + int lineNum; + ArrayList aList; + String tokens[], dummyVar[]; + boolean isProcessed; + + // Insanity check + if (tpSet == null) + return; + + // Process our input + br = new BufferedReader( new InputStreamReader(inStream) ); + lineNum = 0; + dummyVar = new String[1]; + + // Read the lines + while (true) + { + // Bail if the associated task is no longer active + if (aTask != null && aTask.isActive() == false) + return; + + strLine = br.readLine(); + if (strLine == null) + { + // Notify the TokenProcessors of job done + for (TokenProcessor aTP : tpSet) + aTP.flush(); + + // Release the various streams + br.close(); + inStream.close(); + break; + } + lineNum++; + + // Get the tokens out of our string + tokens = null; + aList = aTokenizer.getTokens(strLine); + if (aList.size() > 0) + { + // Transform from a list to an array + tokens = aList.toArray(dummyVar); + + // Process the tokens + isProcessed = false; + for (TokenProcessor aTP : tpSet) + { + isProcessed = aTP.process(tokens, lineNum); + if (isProcessed == true) + break; + } + + // Print out error message + if (isProcessed == false) + { + System.out.println("Unreconized line [" + lineNum + "]: \n" + "\t" + strLine); + } + } + } + } + + /** + * Prompts the user to select a single File + * + * @param aLoaderInfo + * : A object that stores the configuration from method call to method call. + * @param parentComp + * The parent component for the associated FileChooser GUI + * @param aTitleStr + * The title of the FileChooser GUI + * @param ffList + * A List of FileFilters + * @param isSaveDialog + * Whether this FileChooser displays a GUI appropriate for saving a file + * @return The selected file or null + */ + public static File queryUserForFile(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, Collection ffList, boolean isSaveDialog) + { + JFileChooser aFC; + int aVal; + + // Ensure we have a non null LoaderInfo + if (aLoaderInfo == null) + aLoaderInfo = new LoaderInfo(); + + // Set up the FileChooser + aFC = new StandardFileChooser(null); + aFC.setAcceptAllFileFilterUsed(false); + aFC.setDialogTitle(aTitleStr); + aFC.setMultiSelectionEnabled(false); + aLoaderInfo.loadConfig(aFC); + + // Set in the FileFilters + for (FileFilter aFileFilter : ffList) + aFC.addChoosableFileFilter(aFileFilter); + + // Let the user choose a file + if (isSaveDialog == true) + aVal = aFC.showSaveDialog(parentComp); + else + aVal = aFC.showOpenDialog(parentComp); + + // Store off the current settings + aLoaderInfo.saveConfig(aFC); + + // Bail if no file chosen + if (aVal != JFileChooser.APPROVE_OPTION) + return null; + + // Return the file + return aFC.getSelectedFile(); + } + + public static File queryUserForFile(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, FileFilter aFileFilter, boolean isSaveDialog) + { + List ffList; + + ffList = Lists.newArrayList(); + if (aFileFilter != null) + ffList.add(aFileFilter); + + return queryUserForFile(aLoaderInfo, parentComp, aTitleStr, ffList, isSaveDialog); + } + + public static File queryUserForFile(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, boolean isSaveDialog) + { + return queryUserForFile(aLoaderInfo, parentComp, aTitleStr, (FileFilter)null, isSaveDialog); + } + + /** + * Prompts the user to select multiple Files + * + * @param aLoaderInfo + * : A object that stores the configuration from method call to method call. + * @param parentComp + * The parent component for the associated FileChooser GUI + * @param aTitleStr + * The title of the FileChooser GUI + * @param ffList + * A List of FileFilters + * @param isSaveDialog + * Whether this FileChooser displays a GUI appropriate for saving a file + * @return The selected file or null + */ + public static List queryUserForFiles(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, Collection ffList, boolean isSaveDialog) + { + JFileChooser aFC; + List retList; + int aVal; + + // Ensure we have a non null LoaderInfo + if (aLoaderInfo == null) + aLoaderInfo = new LoaderInfo(); + + // Set up the FileChooser + aFC = new StandardFileChooser(null); + aFC.setAcceptAllFileFilterUsed(false); + aFC.setDialogTitle(aTitleStr); + aFC.setMultiSelectionEnabled(true); + aLoaderInfo.loadConfig(aFC); + + // Set in the FileFilters + for (FileFilter aFileFilter : ffList) + aFC.addChoosableFileFilter(aFileFilter); + + // Let the user choose a file + if (isSaveDialog == true) + aVal = aFC.showSaveDialog(parentComp); + else + aVal = aFC.showOpenDialog(parentComp); + + // Store off the current settings + aLoaderInfo.saveConfig(aFC); + + // Bail if no file chosen + if (aVal != JFileChooser.APPROVE_OPTION) + return null; + + // Return a list that is modifiable + retList = Arrays.asList(aFC.getSelectedFiles()); + retList = Lists.newArrayList(retList); + return retList; + } + + public static List queryUserForFiles(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, FileFilter aFileFilter, boolean isSaveDialog) + { + List ffList; + + ffList = Lists.newArrayList(); + if (aFileFilter != null) + ffList.add(aFileFilter); + + return queryUserForFiles(aLoaderInfo, parentComp, aTitleStr, ffList, isSaveDialog); + } + + public static List queryUserForFiles(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, boolean isSaveDialog) + { + return queryUserForFiles(aLoaderInfo, parentComp, aTitleStr, (FileFilter)null, isSaveDialog); + } + + /** + * Prompts the user to select a single folder + * + * @param aLoaderInfo + * : A object that stores the configuration from method call to method call. + * @param parentComp + * The parent component for the associated FileChooser GUI + * @param aTitleStr + * The title of the FileChooser GUI + * @param isSaveDialog + * Whether this FileChooser displays a GUI appropriate for saving a file + * @return The selected file or null + */ + public static File queryUserForPath(LoaderInfo aLoaderInfo, Component parentComp, String aTitleStr, boolean isSaveDialog) + { + JFileChooser aFC; + int aVal; + + // Ensure we have a non null LoaderInfo + if (aLoaderInfo == null) + aLoaderInfo = new LoaderInfo(); + + // Set up the FileChooser + aFC = new StandardFileChooser(null); + aFC.setDialogTitle(aTitleStr); + aFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + aFC.setMultiSelectionEnabled(false); + + aLoaderInfo.loadConfig(aFC); + + // Let the user choose a file + if (isSaveDialog == true) + aVal = aFC.showSaveDialog(parentComp); + else + aVal = aFC.showOpenDialog(parentComp); + + // Store off the current settings + aLoaderInfo.saveConfig(aFC); + + // Bail if no file chosen + if (aVal != JFileChooser.APPROVE_OPTION) + return null; + + // Return the file + return aFC.getSelectedFile(); + } + +} diff --git a/src/glum/io/LoaderInfo.java b/src/glum/io/LoaderInfo.java new file mode 100644 index 0000000..cd1b5ca --- /dev/null +++ b/src/glum/io/LoaderInfo.java @@ -0,0 +1,101 @@ +package glum.io; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; +import glum.zio.raw.ZioRaw; +import glum.zio.util.ZioUtil; + +import java.awt.Dimension; +import java.awt.Point; +import java.io.File; +import java.io.IOException; + +import javax.swing.JFileChooser; + +public class LoaderInfo implements ZioRaw +{ + // Gui vars + private boolean isVisible; + private Point position; + private Dimension dimension; + + // Path vars + private String filePath; + + public LoaderInfo(File aFilePath) + { + isVisible = false; + position = null; + dimension = null; + + filePath = null; + if (aFilePath != null) + filePath = aFilePath.getAbsolutePath(); + } + + public LoaderInfo() + { + this(null); + } + + /** + * Loads the current configuration into aFileChooser + */ + public void loadConfig(JFileChooser aFileChooser) + { + if (position != null) + aFileChooser.setLocation(position); + + if (dimension != null) + { + aFileChooser.setPreferredSize(dimension); + aFileChooser.setSize(dimension); + } + + // Not utilized as this is the incorrect way to load up the standard JFileChooser +// aFileChooser.setVisible(isVisible); + + if (filePath != null) + aFileChooser.setCurrentDirectory(new File(filePath)); + } + + /** + * Stores the current configuration from aFileChooser + */ + public void saveConfig(JFileChooser aFileChooser) + { + position = aFileChooser.getLocation(); + dimension = aFileChooser.getSize(); + isVisible = aFileChooser.isVisible(); + + filePath = aFileChooser.getCurrentDirectory().getAbsolutePath(); + } + + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + aStream.readVersion(0); + + isVisible = aStream.readBool(); + + position = ZioUtil.readPoint(aStream); + dimension = ZioUtil.readDimension(aStream); + + filePath = aStream.readString(); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + aStream.writeVersion(0); + + aStream.writeBool(isVisible); + + ZioUtil.writePoint(aStream, position); + ZioUtil.writeDimension(aStream, dimension); + + aStream.writeString(filePath); + } + +} diff --git a/src/glum/io/NullOutputStream.java b/src/glum/io/NullOutputStream.java new file mode 100644 index 0000000..4047c23 --- /dev/null +++ b/src/glum/io/NullOutputStream.java @@ -0,0 +1,63 @@ +package glum.io; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class NullOutputStream extends DataOutputStream +{ + protected int byteCount; + + /** + * OutputStream used to count bytes that are to be written. + */ + public NullOutputStream() + { + super(null); + + // Redirect the OutputStream set in the constructor from null to this. This is + // needed since we can't pass "this" into the default constructor. Low level + // IO calls are routed into the NullOutputStream and bytes are just counted. + out = this; + + byteCount = 0; + } + + /** + * Returns the number of bytes that have been counted. + */ + public int getNumBytes() + { + return byteCount; + } + + @Override + public void write(int b) throws IOException + { + byteCount++; + } + + @Override + public void write(byte[] aArr) throws IOException + { + byteCount += aArr.length; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + byteCount += len; + } + + @Override + public void flush() throws IOException + { + ; // Nothing to do + } + + @Override + public void close() throws IOException + { + throw new RuntimeException("Unsupported operation."); + } + +} diff --git a/src/glum/io/RegExFileFilter.java b/src/glum/io/RegExFileFilter.java new file mode 100644 index 0000000..6aaeaf5 --- /dev/null +++ b/src/glum/io/RegExFileFilter.java @@ -0,0 +1,59 @@ +package glum.io; + +import java.io.File; +import java.util.regex.Pattern; +import javax.swing.filechooser.FileFilter; + +public class RegExFileFilter extends FileFilter implements java.io.FileFilter +{ + // State vars + private String description; + private Pattern matchPattern; + private boolean allowDirs; + + public RegExFileFilter(String aDescription, String aMatchRegEx, boolean aAllowDirs) + { + description = aDescription; + matchPattern = Pattern.compile(aMatchRegEx); + allowDirs = aAllowDirs; + } + + public RegExFileFilter(String aDescription, String aMatchRegEx) + { + this(aDescription, aMatchRegEx, true); + } + + /** + * Sets whether directories should be allowed + */ + public void setAllowDirectories(boolean aBool) + { + allowDirs = aBool; + } + + @Override + public boolean accept(File aFile) + { + String fileName; + + // Allow directories if appropriate + if (aFile.isDirectory() == true) + return allowDirs; + + // Retrieve the corresponding file name + fileName = aFile.getName(); + + // Test to see if the fileName matches the compiled regex + if (matchPattern.matcher(fileName).matches() == true) + return true; + + return false; + } + + @Override + public String getDescription() + { + return description; + } + +} diff --git a/src/glum/io/SimpleFileFilter.java b/src/glum/io/SimpleFileFilter.java new file mode 100644 index 0000000..b9f522a --- /dev/null +++ b/src/glum/io/SimpleFileFilter.java @@ -0,0 +1,91 @@ +package glum.io; + +import java.io.File; +import java.util.*; +import javax.swing.filechooser.FileFilter; + +public class SimpleFileFilter extends FileFilter implements java.io.FileFilter +{ + // State vars + private Collection extensionList; + private String description; + private boolean allowDirs; + + public SimpleFileFilter(String aDescription) + { + allowDirs = true; + description = aDescription; + extensionList = new LinkedList(); + } + + public SimpleFileFilter(String aDescription, String aExtension) + { + this(aDescription); + addExtension(aExtension); + } + + /** + * Adds a file extension which should be allowed through this filter + */ + public void addExtension(String aExtension) + { + if (aExtension != null) + extensionList.add(aExtension); + } + + /** + * Adds the collections of file extensions which should be allowed through this filter + */ + public void addExtensions(String... extArr) + { + for (String aExtension : extArr) + addExtension(aExtension); + } + + /** + * Sets whether directories should be allowed + */ + public void setAllowDirectories(boolean aBool) + { + allowDirs = aBool; + } + + @Override + public boolean accept(File aFile) + { + String aStr, aFileName; + int aIndex; + + // Allow directories if appropriate + if (aFile.isDirectory() == true) + return allowDirs; + + // Retrieve the corresponding file name + aFileName = aFile.getName(); + + // Ensure the file has an extension + aIndex = aFileName.lastIndexOf('.'); + if (aIndex == -1) + return false; + + // See if aFileName's extension matches any in extensionList + for (String aExt : extensionList) + { + if (aFileName.length() > aExt.length()) + { + aStr = aFileName.substring(aFileName.length() - aExt.length()); + if (aExt.equalsIgnoreCase(aStr) == true) + return true; + } + } + + return false; + } + + @Override + public String getDescription() + { + return description; + } + +} diff --git a/src/glum/io/StandardFileChooser.java b/src/glum/io/StandardFileChooser.java new file mode 100644 index 0000000..2340c54 --- /dev/null +++ b/src/glum/io/StandardFileChooser.java @@ -0,0 +1,52 @@ +package glum.io; + +import java.awt.*; +import javax.swing.*; + +public class StandardFileChooser extends JFileChooser +{ + // Gui vars + private Dialog tDialog; + private Point tPoint; + + /** + * Constructor + */ + public StandardFileChooser(String aFilePath) + { + super(aFilePath); + + tDialog = null; + tPoint = null; + } + + @Override + protected JDialog createDialog(Component parent) throws HeadlessException + { + JDialog dialog = super.createDialog(parent); + + if (tPoint != null) + dialog.setLocation(tPoint); + + tDialog = dialog; + return dialog; + } + + @Override + public void setLocation(Point aPoint) + { + tPoint = aPoint; + if (tDialog != null) + tDialog.setLocation(aPoint); + } + + @Override + public Point getLocation() + { + if (tDialog != null) + return tDialog.getLocation(); + + return super.getLocation(); + } + +} diff --git a/src/glum/io/TokenProcessor.java b/src/glum/io/TokenProcessor.java new file mode 100644 index 0000000..3d1dc75 --- /dev/null +++ b/src/glum/io/TokenProcessor.java @@ -0,0 +1,18 @@ +package glum.io; + +public interface TokenProcessor +{ + /** + * Lets the processor know that it will not be called anymore. This method is + * only called after all data has been read in from the corresponding stream. + * Note this method will never get called if the reading was aborted or an IO + * exception occured. + */ + public void flush(); + + /** + * Returns true if able to handle the tokens + */ + public boolean process(String[] tokens, int lineNum); + +} diff --git a/src/glum/io/WarningTP.java b/src/glum/io/WarningTP.java new file mode 100644 index 0000000..a46255d --- /dev/null +++ b/src/glum/io/WarningTP.java @@ -0,0 +1,58 @@ +package glum.io; + +import java.util.*; + +public class WarningTP implements TokenProcessor +{ + private Map warningSet; + + /** + * Constructor + */ + public WarningTP() + { + warningSet = new LinkedHashMap(); + } + + /** + * add - Adds a new warning to the set of warnings; Note if aMsg is null + * then aInstr will just be ignored + */ + public void add(String aInstr, String aMsg) + { + // Insanity check + if (aInstr == null) + return; + + warningSet.put(aInstr, aMsg); + } + + @Override + public void flush() + { + ; // Nothing to do + } + + @Override + public boolean process(String[] tokens, int lineNum) + { + // Insanity check + if (tokens == null) + return false; + + if (warningSet.containsKey(tokens[0]) == true) + { + String aMsg; + + // Display the aMsg if associated with aInstr + aMsg = warningSet.get(tokens[0]); + if (aMsg instanceof String) + System.out.println("[" + lineNum + "] " + aMsg); + + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/glum/io/token/BaseTokenizer.java b/src/glum/io/token/BaseTokenizer.java new file mode 100644 index 0000000..d7e2fc2 --- /dev/null +++ b/src/glum/io/token/BaseTokenizer.java @@ -0,0 +1,56 @@ +package glum.io.token; + +public abstract class BaseTokenizer implements Tokenizer +{ + // Constants + public static final int MODE_NONE = 0; + public static final int MODE_ANY_POS = 1; + public static final int MODE_FIRST_POS = 2; + + // State vars + protected int commentMode; + + public BaseTokenizer() + { + commentMode = MODE_ANY_POS; + } + + /** + * Sets in the comment mode + */ + public void setCommentMode(int aMode) + { + commentMode = aMode; + } + + /** + * Returns the string with the comments stripped based on the allowable comment style + */ + protected String getCleanString(String inputStr) + { + if (commentMode == MODE_NONE) + { + return inputStr; + } + else if (commentMode == MODE_FIRST_POS) + { + if (inputStr.indexOf(0) == '#') + return ""; + } + else if (commentMode == MODE_ANY_POS) + { + int index; + + index = inputStr.indexOf('#'); + if (index != -1) + inputStr = inputStr.substring(0, index); + } + else + { + throw new RuntimeException("Error: commentMode:" + commentMode); + } + + return inputStr; + } + +} diff --git a/src/glum/io/token/MatchTokenizer.java b/src/glum/io/token/MatchTokenizer.java new file mode 100644 index 0000000..8dfa1b2 --- /dev/null +++ b/src/glum/io/token/MatchTokenizer.java @@ -0,0 +1,56 @@ +package glum.io.token; + +import java.util.*; +import java.util.regex.*; + +public class MatchTokenizer extends BaseTokenizer +{ + private Pattern myPattern; + private boolean autoStripQuotes; + + public MatchTokenizer(String aRegEx) + { + // Compile the pattern + myPattern = Pattern.compile(aRegEx); + + autoStripQuotes = true; + } + + /** + * Sets the mode of whether double quotes will be automatically stripped + */ + public void setAutoStripQuotes(boolean aBool) + { + autoStripQuotes = aBool; + } + + @Override + public ArrayList getTokens(String inputStr) + { + Matcher aMatcher; + ArrayList aList; + String aMatch; + + // Clean up the input string before processing + inputStr = getCleanString(inputStr); + + aMatcher = myPattern.matcher(inputStr); + if (aMatcher == null) + return null; + + aList = new ArrayList(); + while (aMatcher.find() == true) + { + aMatch = aMatcher.group(); + + // Strip the (double) quotes if requested + if (autoStripQuotes == true) + aMatch = TokenUtil.getRawStr(aMatch); + + aList.add(aMatch); + } + + return aList; + } + +} diff --git a/src/glum/io/token/SplitTokenizer.java b/src/glum/io/token/SplitTokenizer.java new file mode 100644 index 0000000..9470cca --- /dev/null +++ b/src/glum/io/token/SplitTokenizer.java @@ -0,0 +1,33 @@ +package glum.io.token; + +import java.util.*; +import java.util.regex.*; + +import com.google.common.collect.Lists; + +public class SplitTokenizer extends BaseTokenizer +{ + private Pattern myPattern; + + public SplitTokenizer(String aRegEx) + { + // Compile the pattern + myPattern = Pattern.compile(aRegEx); + } + + @Override + public ArrayList getTokens(String inputStr) + { + String[] tokenArr; + ArrayList retList; + + // Clean up the input string before processing + inputStr = getCleanString(inputStr); + + tokenArr = myPattern.split(inputStr, -1); + + retList = Lists.newArrayList(tokenArr); + return retList; + } + +} diff --git a/src/glum/io/token/TokenUtil.java b/src/glum/io/token/TokenUtil.java new file mode 100644 index 0000000..b521854 --- /dev/null +++ b/src/glum/io/token/TokenUtil.java @@ -0,0 +1,76 @@ +package glum.io.token; + +public class TokenUtil +{ + /** + * Returns the actual string contents sans quotes + */ + public static String getRawStr(String aStr) + { + // Insanity check + if (aStr == null) + return null; + + // Test for the empty string + if (aStr.length() < 3) + return aStr; + + // Return the string sans quote + if (aStr.charAt(0) == '"') + return aStr.substring(1, aStr.length() - 1); + + // No quote section found + return aStr; + } + + /** + * Utility method to convert a wild card exression to a regular + * expression. Currently only the special chars '?', '*' are supported. + * Source: http://www.rgagnon.com/javadetails/java-0515.html + */ + public static String convertWildCardToRegEx(String wildcard) + { + StringBuffer regex; + + regex = new StringBuffer(wildcard.length()); + regex.append('^'); + for (int i = 0, is = wildcard.length(); i < is; i++) + { + char c = wildcard.charAt(i); + switch (c) + { + case '*': + regex.append(".*"); + break; + + case '?': + regex.append("."); + break; + + // escape special regexp-characters + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + regex.append("\\"); + regex.append(c); + break; + + default: + regex.append(c); + break; + } + } + + regex.append('$'); + return (regex.toString()); + } + +} diff --git a/src/glum/io/token/Tokenizer.java b/src/glum/io/token/Tokenizer.java new file mode 100644 index 0000000..10150eb --- /dev/null +++ b/src/glum/io/token/Tokenizer.java @@ -0,0 +1,12 @@ +package glum.io.token; + +import java.util.ArrayList; + +public interface Tokenizer +{ + /** + * Returns all the tokens that match the pattern from the input + */ + public ArrayList getTokens(String inputStr); + +} diff --git a/src/glum/logic/LogicChunk.java b/src/glum/logic/LogicChunk.java new file mode 100644 index 0000000..f42e4b3 --- /dev/null +++ b/src/glum/logic/LogicChunk.java @@ -0,0 +1,13 @@ +package glum.logic; + +public interface LogicChunk +{ + /** + * LogicChunk interface methods + */ + public void activate(); + public void dispose(); + public String getName(); + public String getVersion(); + +} diff --git a/src/glum/logic/LogicChunkEngine.java b/src/glum/logic/LogicChunkEngine.java new file mode 100644 index 0000000..3e75776 --- /dev/null +++ b/src/glum/logic/LogicChunkEngine.java @@ -0,0 +1,371 @@ +package glum.logic; + +import glum.io.token.MatchTokenizer; +import glum.reflect.ReflectUtil; +import glum.registry.Registry; + +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.lang.reflect.Constructor; +import java.net.URL; +import java.util.*; + +import javax.swing.*; + +public class LogicChunkEngine implements ActionListener +{ + // State vars + private Registry refRegistry; + private JMenuBar menuBar; + private Map menuMap; + private ShutdownHook shutdownHook; + + public LogicChunkEngine(Registry aRegistry, URL aURL, String aAppName) + { + boolean isHeadless; + + refRegistry = aRegistry; + menuMap = null; + menuBar = null; + + // Are we headless + isHeadless = GraphicsEnvironment.isHeadless(); + + // Install our custom shutdown logic + shutdownHook = new ShutdownHook(this, aAppName); + Runtime.getRuntime().addShutdownHook(shutdownHook); + + // Load up the LogicChunks + loadLogicChunks(aURL, isHeadless); + } + + /** + * Notifies all of the LogicChunks to perform the dispose operation. + */ + public void dispose() + { + for (LogicChunk aLogicChunk : menuMap.values()) + { + try + { + if (aLogicChunk != null) + aLogicChunk.dispose(); + } + catch (Exception aExp) + { + System.out.println("Failed to dispose LogicChunk. Exception:"); + aExp.printStackTrace(); + } + } + + menuBar = null; + menuMap = null; + } + + /** + * Returns the MenuBar associated with the LogicChunkEngine + */ + public JMenuBar getMenuBar() + { + return menuBar; + } + + /** + * Returns all of the LogicChunks in this engine + */ + public Collection getLogicChunks() + { + return menuMap.values(); + } + + /** + * Configures the shutdown hook to exit quickly by not waiting for daemon threads. + */ + public void setQuickExit(boolean aBool) + { + shutdownHook.setQuickExit(aBool); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + LogicChunk aLogicChunk; + + // Insanity check + if (aEvent == null || menuMap == null) + return; + + // Find the associated LogicChunk + aLogicChunk = (LogicChunk)menuMap.get( aEvent.getSource() ); + + // Activate the MenuItem + if (aLogicChunk != null) + aLogicChunk.activate(); + } + + /** + * Helper method to load up the LogicChunk described at aURL + */ + private void loadLogicChunks(URL aURL, boolean isHeadless) + { + InputStream inStream; + BufferedReader br; + MatchTokenizer aTokenizer; + String regEx; + ArrayList tokenList; + String[] tokens; + int numTokens; + LinkedList currMenuList; + JMenuBar aMenuBar; + JMenu currMenu; + JMenuItem aMenuItem; + String strLine; + String aLoc; + LogicChunk logicChunk; + + aLoc = aURL.toString(); + + currMenuList = new LinkedList(); + aMenuItem = null; + menuMap = new LinkedHashMap(); + + // Build our tokenizer + regEx = "(#.*)|([a-zA-Z0-9\\.]+)|(\"[^\"\\r\\n]*\")|([a-zA-Z0-9\\.]+)"; + aTokenizer = new MatchTokenizer(regEx); + + // Create the MenuBar only if we are not headless + aMenuBar = null; + if (isHeadless == false) + aMenuBar = new JMenuBar(); + + // Process our input + try + { + inStream = aURL.openStream(); + br = new BufferedReader( new InputStreamReader(inStream) ); + + // Read the lines + while (true) + { + strLine = br.readLine(); + if (strLine == null) + { + br.close(); + inStream.close(); + break; + } + + // Get the tokens out of strLine + tokenList = aTokenizer.getTokens(strLine); + + tokens = tokenList.toArray(new String[0]); + numTokens = tokens.length; + + // Process the tokens + if (numTokens == 0) + { + ; // Empty line + } + else if ( (isHeadless == true) + && (tokens[0].equals("Menu") == true || tokens[0].equals("MenuItem") == true + || tokens[0].equals("SubMenu") || tokens[0].equals("EndSubMenu")) ) + { + System.out.println("Ignoring:" + tokens[0] + " command. Running in headless environment."); + System.out.println("\tTokens: " + tokens); + } + else if (tokens[0].equals("Menu") == true && numTokens == 2) + { + JMenu aMenu; + + // Create a new menu + aMenu = new JMenu(tokens[1]); + aMenuBar.add(aMenu); + + // Reset the menu list + currMenuList.clear(); + currMenuList.addLast(aMenu); + } + else if (tokens[0].equals("SubMenu") == true && numTokens == 2) + { + JMenu aMenu; + + // Get the current menu + if (currMenuList.isEmpty() == true) + currMenu = null; + else + currMenu = currMenuList.getLast(); + + if (currMenu == null) + { + System.out.println("[" + aLoc + "]: Warning no parent menu found for the SubMenu.\n"); + } + else + { + // Create and add into the current working menu + aMenu = new JMenu(tokens[1]); + currMenu.add(aMenu); + + currMenuList.addLast(aMenu); + } + } + else if (tokens[0].equals("EndSubMenu") == true && numTokens == 1) + { + if (currMenuList.isEmpty() == true) + System.out.println("[" + aLoc + "]: Warning no parent sub menu found. Instr: EndSubMenu\n"); + else + currMenuList.removeLast(); + } + + // Process the various types of MenuItems + else if (tokens[0].equals("AutoItem") == true && numTokens == 3) + { + // Build the auto item and add it only into our MenuMap + logicChunk = loadLogicChunkInstance(refRegistry, tokens[2], tokens[1], aLoc); + if (logicChunk != null) + { + menuMap.put(logicChunk, logicChunk); + } + } + else if (tokens[0].equals("MenuItem") == true && (numTokens == 2 || numTokens == 3)) + { + // Get the current menu + if (currMenuList.isEmpty() == true) + currMenu = null; + else + currMenu = currMenuList.getLast(); + + if (currMenu == null) + { + System.out.println("[" + aLoc + "]: Warning no parent sub menu found. Instr: MenuItem.\n"); + } + else if (tokens[1].equals("Break") == true) + { + currMenu.addSeparator(); + } + else + { + // Build the menu item + if (numTokens == 2) + { + aMenuItem = new JMenuItem(tokens[1]); + aMenuItem.setEnabled(false); + } + else + { + // Try to build the LogicChunk and load it into our MenuMap + logicChunk = loadLogicChunkInstance(refRegistry, tokens[2], tokens[1], aLoc); + + // Form the MenuItem or Menu + if (logicChunk instanceof SubMenuChunk) + aMenuItem = new JMenu(tokens[1]); + else + aMenuItem = new JMenuItem(tokens[1]); + aMenuItem.addActionListener(this); + + + // Associate the MenuItem with the LogicChunk + if (logicChunk != null) + menuMap.put(aMenuItem, logicChunk); + + // Notify MenuItemChunk/SubMenuChunk of the associated menu item + if (logicChunk instanceof MenuItemChunk) + ((MenuItemChunk)logicChunk).setMenuItem(aMenuItem); + if (logicChunk instanceof SubMenuChunk) + ((SubMenuChunk)logicChunk).setMenu((JMenu)aMenuItem); + } + currMenu.add(aMenuItem); + } + } + else + { + System.out.println("Unreconized line. Instruction: -" + tokens[0] + "-"); + System.out.println("\tTokens: " + tokens); + } + } + + } + catch (FileNotFoundException e) + { + System.out.println("File not found: " + aLoc); + return; + } + catch (IOException e) + { + System.out.println("Ioexception occured in: LogicChunkEngine.loadLogicChunks()"); + return; + } + + menuBar = aMenuBar; + } + + /** + * Attempts to load up the logicChunk with the specified aLabel. If that fails then will attempt to + * construct the LogicChunk using the default constructor. + */ + private static LogicChunk loadLogicChunkInstance(Registry aRegistry, String aFullClassPath, String aLabel, String aLoc) + { + Class rawClass; + Constructor rawConstructor; + Class parmTypes1[] = {Registry.class, String.class}; + Class parmTypes2[] = {String.class}; + Object parmValues[]; + + // Insanity check + if (aFullClassPath == null) + return null; + + try + { + // Retrieve the class and ensure it is a LogicChunk + rawClass = Class.forName(aFullClassPath); + if (LogicChunk.class.isAssignableFrom(rawClass) == false) + { + System.out.println("Failure: " + aFullClassPath + " is not a LogicChunk!"); + System.out.println("\tLocation: " + aLoc + "\n"); + return null; + } + + // Try the 1st preferred constructor + rawConstructor = ReflectUtil.getConstructorSafe(rawClass, parmTypes1); + if (rawConstructor != null) + { + parmValues = new Object[2]; + parmValues[0] = aRegistry; + parmValues[1] = aLabel; + + return (LogicChunk)rawConstructor.newInstance(parmValues); + } + + // Try the 2nd preferred constructor + rawConstructor = ReflectUtil.getConstructorSafe(rawClass, parmTypes2); + if (rawConstructor != null) + { + parmValues = new Object[1]; + parmValues[0] = aLabel; + + return (LogicChunk)rawConstructor.newInstance(parmValues); + } + + // Just use the default constructor + else + { + return (LogicChunk)rawClass.newInstance(); + } + } + catch (ClassNotFoundException aExp) + { + System.out.println("Failure: " + aFullClassPath + " not found."); + System.out.println("\tLocation: " + aLoc + "\n"); + } + catch (Exception aExp) + { + // Unknown Exception + aExp.printStackTrace(); + } + + return null; + } + +} diff --git a/src/glum/logic/MenuItemChunk.java b/src/glum/logic/MenuItemChunk.java new file mode 100644 index 0000000..0f8fce7 --- /dev/null +++ b/src/glum/logic/MenuItemChunk.java @@ -0,0 +1,12 @@ +package glum.logic; + +import javax.swing.*; + +public interface MenuItemChunk +{ + /** + * MenuItemChunk interface methods + */ + public void setMenuItem(JMenuItem aMI); + +} diff --git a/src/glum/logic/ShutdownHook.java b/src/glum/logic/ShutdownHook.java new file mode 100644 index 0000000..63a4d50 --- /dev/null +++ b/src/glum/logic/ShutdownHook.java @@ -0,0 +1,171 @@ +package glum.logic; + +import glum.util.ThreadUtil; + +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * Shutdown logic that attempts to properly shutdown the refLogicChunkEngine. This logic assumes + * that any non daemon threads that appear after the creation of this shutdown hook are a direct + * result of the LogicChunkEngine. Any threads that are started due to a LogicChunkEngine should + * be properly stopped whenever the dispose() method is called on the corresponding LogicChunk. + * a + *

  • This shutdown hook should be installed via Runtime.getRuntime().addShutdownHook(). + *
  • This shutdown hook should be installed before aLogicChunkEngine.loadLogicChunks() is called. + *
  • This class should remain package visible + */ +class ShutdownHook extends Thread +{ + // State vars + private Set ignoreThreadSet; + private LogicChunkEngine refLogicChunkEngine; + private String appName; + private boolean doAbortExit; + private boolean doQuickExit; + + public ShutdownHook(LogicChunkEngine aLogicChunkEngine, String aAppName) + { + refLogicChunkEngine = aLogicChunkEngine; + appName = aAppName; + doAbortExit = false; + doQuickExit = false; + + // Form the list of currently running non daemon threads that can be ignored + // Any non daemon threads that appear are assumed to be started via a LogicChunk and thus + // should be properly stopped whenever the dispose() method is called on the LogicChunk. + ignoreThreadSet = Sets.newHashSet(); + ignoreThreadSet.addAll(Thread.getAllStackTraces().keySet()); + ignoreThreadSet.add(this); + + // Set in the preferred name of this thread + setName("thread-" + getClass().getSimpleName()); + } + + /** + * Configures the shutdown hook to exit immediately by skipping the shutdown of the refLogicChunkEngine. + *

    + * Note the associated LogicChunks dispose method will never be called. + */ + public void setAbortExit(boolean aBool) + { + doAbortExit = aBool; + } + + /** + * Configures the shutdown hook to exit quickly by not waiting for daemon threads. + */ + public void setQuickExit(boolean aBool) + { + doQuickExit = aBool; + } + + @Override + public void run() + { + List origList, currList; + ThreadGroup aThreadGroup; + int cntAttempts; + + // Bail if we have been marked to abort + if (doAbortExit == true) + { + System.out.println(appName + " has been aborted!"); + return; + } + + // Shutdown the LogicChunkEngine + refLogicChunkEngine.dispose(); + + // Bail if we are configured to exit quickly + if (doQuickExit == true) + return; + + // Ensure all non daemon threads are dead + origList = Lists.newArrayList(); + currList = getBlockList(); + + cntAttempts = 1; + while (currList.isEmpty() == false) + { + // Dump out the blocking threads, whenever there is a change + if (origList.size() != currList.size()) + { + System.out.println("Shutdown logic blocked by " + currList.size() + " threads.."); + for (Thread aThread : currList) + { + aThreadGroup = aThread.getThreadGroup(); + if (aThreadGroup != null) + System.out.println(" Waiting for non daemon thread to die: " + aThread.getName() + " threadGroup: " + aThreadGroup.getName()); + else + System.out.println(" Waiting for non daemon thread to die: " + aThread.getName() + " threadGroup: null"); + + } + System.out.println(); + + origList = currList; + } + + // Give the blocked threads some time to exit + ThreadUtil.safeSleep(1000); + + // Retrieve the updated list of blocking threads + currList = getBlockList(); + + // Bail if too many failed attempts + if (cntAttempts >= 7 & currList.isEmpty() == false) + { + // Print out the stack trace of all active threads + System.out.println("Shutdown logic blocked by " + currList.size() + " threads.."); + for (Thread aThread : currList) + { + System.out.println(" [" + aThread.getName() + "] StackTrace:"); + for (StackTraceElement aItem : aThread.getStackTrace()) + System.out.println(" " + aItem.toString()); + } + + System.out.println("\nAborting " + appName + ". Waited too long..."); + break; + } + + cntAttempts++; + } + + // Let the user know that we were properly terminated + System.out.println(appName + " has been shutdown."); + } + + /** + * Helper method to retrieve the set of non daemon threads that we are waiting on to finish executing + *
    Note any thread with the following name pattern will be ignored: + * AWT-*, DestroyJavaVM + */ + private List getBlockList() + { + List fullList; + List blockList; + String name; + + blockList = Lists.newLinkedList(); + + fullList = Lists.newLinkedList(Thread.getAllStackTraces().keySet()); + for (Thread aThread : fullList) + { + name = aThread.getName(); + + // Record the non daemon threads that are still alive and not in the ignoreThreadSet or with a known name + if (aThread.isDaemon() == false && aThread.isAlive() == true && ignoreThreadSet.contains(aThread) == false) + { + // Only add the thread if the name is not one of a well defined name such as: AWT-*, DestroyJavaVM + if ((name.startsWith("AWT-") == true || name.startsWith("DestroyJavaVM") == true) == false) + blockList.add(aThread); + } + } + + return blockList; + } + +} diff --git a/src/glum/logic/SubMenuChunk.java b/src/glum/logic/SubMenuChunk.java new file mode 100644 index 0000000..d2f2b54 --- /dev/null +++ b/src/glum/logic/SubMenuChunk.java @@ -0,0 +1,12 @@ +package glum.logic; + +import javax.swing.*; + +public interface SubMenuChunk +{ + /** + * MenuItemChunk interface methods + */ + public void setMenu(JMenu aMenu); + +} diff --git a/src/glum/logic/dock/FrontendLoadMI.java b/src/glum/logic/dock/FrontendLoadMI.java new file mode 100644 index 0000000..50c5d8a --- /dev/null +++ b/src/glum/logic/dock/FrontendLoadMI.java @@ -0,0 +1,155 @@ +package glum.logic.dock; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Set; +import javax.swing.JMenu; +import javax.swing.JMenuItem; + +import glum.logic.LogicChunk; +import glum.logic.SubMenuChunk; +import glum.registry.Registry; + +import bibliothek.gui.DockFrontend; +import bibliothek.gui.Dockable; +import bibliothek.gui.dock.event.DockFrontendListener; + +public class FrontendLoadMI implements LogicChunk, SubMenuChunk, DockFrontendListener, ActionListener +{ + protected DockFrontend refFrontend; + protected JMenu refMenu; + + public FrontendLoadMI(Registry aRegistry, String aLabel) + { + refFrontend = aRegistry.getSingleton(DockFrontend.class, DockFrontend.class); + refFrontend.addFrontendListener(this); + } + + @Override + public void actionPerformed(ActionEvent aEvent) + { + Object aSource; + String aName; + + aSource = aEvent.getSource(); + if (aSource instanceof JMenuItem) + { + aName = ((JMenuItem)aSource).getText(); + refFrontend.load(aName); + } + } + + @Override + public void activate() + { + ; // Nothing to do + } + + @Override + public void dispose() + { + } + + @Override + public String getName() + { + return "DockFrontend Configuration Loader"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + + @Override + public void setMenu(JMenu aMenu) + { + refMenu = aMenu; + updateGui(); + } + + @Override + public void hidden(DockFrontend frontend, Dockable dockable) + { + ; // Nothing to do + } + + @Override + public void shown(DockFrontend frontend, Dockable dockable) + { + ; // Nothing to do + } + + @Override + public void added(DockFrontend frontend, Dockable dockable) + { + ; // Nothing to do + } + + @Override + public void removed(DockFrontend frontend, Dockable dockable) + { + ; // Nothing to do + } + + @Override + public void hideable(DockFrontend frontend, Dockable dockable, boolean hideable) + { + ; // Nothing to do + } + + @Override + public void loaded(DockFrontend frontend, String name) + { + ; // Nothing to do + } + + @Override + public void read(DockFrontend frontend, String name) + { + updateGui(); + } + + @Override + public void saved(DockFrontend frontend, String name) + { + updateGui(); + } + + @Override + public void deleted(DockFrontend frontend, String name) + { + updateGui(); + } + + /** + * Utility method to keep the refMenu in sync with the available dock + * configurations + */ + protected void updateGui() + { + Set currSet; + JMenuItem tmpMI; + + // Remove the old items + refMenu.removeAll(); + + // Add all of the current configurations + currSet = refFrontend.getSettings(); + for (String aStr : currSet) + { + // Do not add hidden configurations + if (aStr.charAt(0) != '.') + { + tmpMI = new JMenuItem(aStr); + tmpMI.addActionListener(this); + refMenu.add(tmpMI); + } + } + + // Ensure we have items (to be enabled) + refMenu.setEnabled(currSet.size() > 0); + } + +} diff --git a/src/glum/logic/dock/FrontendManageMI.java b/src/glum/logic/dock/FrontendManageMI.java new file mode 100644 index 0000000..37e2a7e --- /dev/null +++ b/src/glum/logic/dock/FrontendManageMI.java @@ -0,0 +1,50 @@ +package glum.logic.dock; + +import javax.swing.JFrame; + + +import bibliothek.gui.DockFrontend; + +import glum.gui.dock.FrontendManageConfigPanel; +import glum.logic.LogicChunk; +import glum.registry.Registry; + +public class FrontendManageMI implements LogicChunk +{ + private FrontendManageConfigPanel myPanel; + + public FrontendManageMI(Registry aRegistry, String aLabel) + { + JFrame aFrame; + DockFrontend aFrontend; + + aFrame = aRegistry.getSingleton("root.window", JFrame.class); + aFrontend = aRegistry.getSingleton(DockFrontend.class, DockFrontend.class); + + myPanel = new FrontendManageConfigPanel(aFrame, aFrontend); + } + + @Override + public void activate() + { + myPanel.setVisible(true); + } + + @Override + public void dispose() + { + } + + @Override + public String getName() + { + return "DockFrontend Configuration Manager"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + +} diff --git a/src/glum/logic/dock/FrontendSaveMI.java b/src/glum/logic/dock/FrontendSaveMI.java new file mode 100644 index 0000000..c556d6d --- /dev/null +++ b/src/glum/logic/dock/FrontendSaveMI.java @@ -0,0 +1,49 @@ +package glum.logic.dock; + +import javax.swing.JFrame; + +import glum.gui.dock.FrontendAddConfigPanel; +import glum.logic.LogicChunk; +import glum.registry.Registry; + +import bibliothek.gui.DockFrontend; + +public class FrontendSaveMI implements LogicChunk +{ + protected FrontendAddConfigPanel myPanel; + + public FrontendSaveMI(Registry aRegistry, String aLabel) + { + JFrame aFrame; + DockFrontend aFrontend; + + aFrame = aRegistry.getSingleton("root.window", JFrame.class); + aFrontend = aRegistry.getSingleton(DockFrontend.class, DockFrontend.class); + + myPanel = new FrontendAddConfigPanel(aFrame, aFrontend); + } + + @Override + public void activate() + { + myPanel.setVisible(true); + } + + @Override + public void dispose() + { + } + + @Override + public String getName() + { + return "DockFrontend Configuration Saver"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + +} diff --git a/src/glum/logic/dock/FrontendShowMI.java b/src/glum/logic/dock/FrontendShowMI.java new file mode 100644 index 0000000..337e676 --- /dev/null +++ b/src/glum/logic/dock/FrontendShowMI.java @@ -0,0 +1,73 @@ +package glum.logic.dock; + +import javax.swing.JMenuItem; + +import glum.logic.LogicChunk; +import glum.logic.MenuItemChunk; +import glum.registry.Registry; + +import bibliothek.gui.DockFrontend; +import bibliothek.gui.Dockable; + +public class FrontendShowMI implements LogicChunk, MenuItemChunk +{ + // State vars + protected DockFrontend refFrontend; + protected String labelStr; + protected String refName; + + public FrontendShowMI(Registry aRegistry, String aLabel) + { + String[] tokens; + + refFrontend = aRegistry.getSingleton(DockFrontend.class, DockFrontend.class); + + tokens = aLabel.split(":"); + if (tokens.length != 2) + throw new RuntimeException("Invalid label specification for LogicChunk.\n Label: " + aLabel); + + labelStr = tokens[0]; + refName = tokens[1]; + } + + @Override + public void activate() + { + Dockable aDockable; + + aDockable = refFrontend.getDockable(refName); + if (aDockable == null) + { + System.out.println("Failed to locate a dockable with the name: " + refName); + return; + } + +// refFrontend.hide(aDockable); + refFrontend.show(aDockable); + aDockable.getDockParent().setFrontDockable(aDockable); + } + + @Override + public void dispose() + { + } + + @Override + public String getName() + { + return "DockFrontend Dock Shower"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + + @Override + public void setMenuItem(JMenuItem aMI) + { + aMI.setText(labelStr); + } + +} diff --git a/src/glum/logic/misc/ConsoleEchoMI.java b/src/glum/logic/misc/ConsoleEchoMI.java new file mode 100644 index 0000000..324c41f --- /dev/null +++ b/src/glum/logic/misc/ConsoleEchoMI.java @@ -0,0 +1,63 @@ +package glum.logic.misc; + +import javax.swing.JMenuItem; + +import glum.logic.LogicChunk; +import glum.logic.MenuItemChunk; +import glum.registry.Registry; + +/** + * LogicChunk used to echo a message to the console. + */ +public class ConsoleEchoMI implements LogicChunk, MenuItemChunk +{ + private String message; + private String title; + + public ConsoleEchoMI(Registry aRegistry, String aLabel) + { + String[] strArr; + + title = aLabel; + message = "No message specified."; + + // Customize the message and MenuItem title + strArr = aLabel.split(":"); + if (strArr.length == 2) + { + title = strArr[0]; + message = strArr[1]; + } + } + + @Override + public void activate() + { + System.out.println(message); + } + + @Override + public void dispose() + { + ; // Nothing to do + } + + @Override + public String getName() + { + return "Console Echo MI"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + + @Override + public void setMenuItem(JMenuItem aMI) + { + aMI.setText(title); + } + +} diff --git a/src/glum/logic/misc/MemoryDialogMI.java b/src/glum/logic/misc/MemoryDialogMI.java new file mode 100644 index 0000000..15dde61 --- /dev/null +++ b/src/glum/logic/misc/MemoryDialogMI.java @@ -0,0 +1,47 @@ +package glum.logic.misc; + +import glum.gui.dialog.MemoryUtilDialog; +import glum.logic.LogicChunk; +import glum.registry.Registry; + +import javax.swing.JFrame; + +public class MemoryDialogMI implements LogicChunk +{ + private JFrame refMainFrame; + private MemoryUtilDialog myDialog; + + public MemoryDialogMI(Registry aRegistry, String aLabel) + { + refMainFrame = aRegistry.getSingleton("root.window", JFrame.class); + myDialog = null; + } + + @Override + public void activate() + { + // Lazy initialization + if (myDialog == null) + myDialog = new MemoryUtilDialog(refMainFrame); + + myDialog.setVisible(true); + } + + @Override + public void dispose() + { + } + + @Override + public String getName() + { + return "Memory Util Dialog"; + } + + @Override + public String getVersion() + { + return "0.1"; + } + +} diff --git a/src/glum/math/FastMath.java b/src/glum/math/FastMath.java new file mode 100644 index 0000000..a4475fd --- /dev/null +++ b/src/glum/math/FastMath.java @@ -0,0 +1,614 @@ +package glum.math; + +final public class FastMath +{ + + private FastMath(){} + + + /** A "close to zero" double epsilon value for use*/ + public static final double DBL_EPSILON = 2.220446049250313E-16d; + + + /** A "close to zero" double epsilon value for use*/ + public static final double FLT_EPSILON = 1.1920928955078125E-7d; + + + /** A "close to zero" double epsilon value for use*/ + public static final double ZERO_TOLERANCE = 0.0001d; + + public static final double ONE_THIRD = 1.0/3.0; + + + /** The value PI as a double. (180 degrees) */ + public static final double PI = Math.PI; + + + /** The value 2PI as a double. (360 degrees) */ + public static final double TWO_PI = 2.0 * PI; + + + /** The value PI/2 as a double. (90 degrees) */ + public static final double HALF_PI = 0.5 * PI; + + + /** The value PI/4 as a double. (45 degrees) */ + public static final double QUARTER_PI = 0.25 * PI; + + + /** The value 1/PI as a double. */ + public static final double INV_PI = 1.0 / PI; + + + /** The value 1/(2PI) as a double. */ + public static final double INV_TWO_PI = 1.0 / TWO_PI; + + + /** A value to multiply a degree value by, to convert it to radians. */ + public static final double DEG_TO_RAD = PI / 180.0; + + + /** A value to multiply a radian value by, to convert it to degrees. */ + public static final double RAD_TO_DEG = 180.0 / PI; + + + /** A precreated random object for random numbers. */ + //! public static final Random rand = new Random(System.currentTimeMillis()); + + + + + /** + * Returns true if the number is a power of 2 (2,4,8,16...) + * + * A good implementation found on the Java boards. note: a number is a power + * of two if and only if it is the smallest number with that number of + * significant bits. Therefore, if you subtract 1, you know that the new + * number will have fewer bits, so ANDing the original number with anything + * less than it will give 0. + * + * @param number + * The number to test. + * @return True if it is a power of two. + */ + public static boolean isPowerOfTwo(int number) { + return (number > 0) && (number & (number - 1)) == 0; + } + + public static int nearestPowerOfTwo(int number) { + return (int)Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); + } + + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param percent + * Percent value to use. + * @param startValue + * Begining value. 0% of f + * @param endValue + * ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static double LERP(double percent, double startValue, double endValue) { + if (startValue == endValue) return startValue; + return ((1 - percent) * startValue) + (percent * endValue); + } + + + + + /** + * Returns the arc cosine of an angle given in radians.
    + * Special cases: + *

    • If fValue is smaller than -1, then the result is PI. + *
    • If the argument is greater than 1, then the result is 0.
    + * @param fValue The angle, in radians. + * @return fValue's acos + * @see java.lang.Math#acos(double) + */ + public static double acos(double fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) + return Math.acos(fValue); + + return 0.0f; + } + + return PI; + } + + + /** + * Returns the arc sine of an angle given in radians.
    + * Special cases: + *
    • If fValue is smaller than -1, then the result is -HALF_PI. + *
    • If the argument is greater than 1, then the result is HALF_PI.
    + * @param fValue The angle, in radians. + * @return fValue's asin + * @see java.lang.Math#asin(double) + */ + public static double asin(double fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) + return Math.asin(fValue); + + return HALF_PI; + } + + return -HALF_PI; + } + + + /** + * Returns the arc tangent of an angle given in radians.
    + * @param fValue The angle, in radians. + * @return fValue's asin + * @see java.lang.Math#atan(double) + */ + public static double atan(double fValue) { + return Math.atan(fValue); + } + + + /** + * A direct call to Math.atan2. + * @param fY + * @param fX + * @return Math.atan2(fY,fX) + * @see java.lang.Math#atan2(double, double) + */ + public static double atan2(double fY, double fX) { + return Math.atan2(fY, fX); + } + + + /** + * Rounds a fValue up. A call to Math.ceil + * @param fValue The value. + * @return The fValue rounded up + * @see java.lang.Math#ceil(double) + */ + public static double ceil(double fValue) { + return Math.ceil(fValue); + } + + /** + * Fast Trig functions for x86. This forces the trig functiosn to stay + * within the safe area on the x86 processor (-45 degrees to +45 degrees) + * The results may be very slightly off from what the Math and StrictMath + * trig functions give due to rounding in the angle reduction but it will be + * very very close. + * + * note: code from wiki posting on java.net by jeffpk + */ + public static double reduceSinAngle(double radians) { + radians %= TWO_PI; // put us in -2PI to +2PI space + if (Math.abs(radians) > PI) { // put us in -PI to +PI space + radians = radians - (TWO_PI); + } + if (Math.abs(radians) > HALF_PI) {// put us in -PI/2 to +PI/2 space + radians = PI - radians; + } + + + return radians; + } + + + /** + * Returns sine of a value. + * + * note: code from wiki posting on java.net by jeffpk + * + * @param fValue + * The value to sine, in radians. + * @return The sine of fValue. + * @see java.lang.Math#sin(double) + */ +/*! public static double sin(double fValue) { + fValue = reduceSinAngle(fValue); // limits angle to between -PI/2 and +PI/2 + if (Math.abs(fValue)<=Math.PI/4){ + return Math.sin(fValue); + } + + return Math.cos(Math.PI/2-fValue); + } +*/ + + /** + * Returns cos of a value. + * + * @param fValue + * The value to cosine, in radians. + * @return The cosine of fValue. + * @see java.lang.Math#cos(double) + */ +/*! public static double cos(double fValue) { + return sin(fValue+HALF_PI); + } +*/ + + /** + * Returns E^fValue + * @param fValue Value to raise to a power. + * @return The value E^fValue + * @see java.lang.Math#exp(double) + */ +/*! public static double exp(double fValue) { + return Math.exp(fValue); + } +*/ + + /** + * Returns Absolute value of a double. + * @param fValue The value to abs. + * @return The abs of the value. + * @see java.lang.Math#abs(double) + */ + public static double abs(double fValue) { + if (fValue < 0) return -fValue; + return fValue; + } + + + /** + * Returns a number rounded down. + * @param fValue The value to round + * @return The given number rounded down + * @see java.lang.Math#floor(double) + */ + public static double floor(double fValue) { + return Math.floor(fValue); + } + + + /** + * Returns 1/sqrt(fValue) + * @param fValue The value to process. + * @return 1/sqrt(fValue) + * @see java.lang.Math#sqrt(double) + */ + public static double invSqrt(double fValue) { + return (1.0f / Math.sqrt(fValue)); + } + + + /** + * Returns the log base E of a value. + * @param fValue The value to log. + * @return The log of fValue base E + * @see java.lang.Math#log(double) + */ + public static double log(double fValue) { + return Math.log(fValue); + } + + /** + * Returns the logarithm of value with given base, calculated as log(value)/log(base), + * so that pow(base, return)==value (contributed by vear) + * @param value The value to log. + * @param base Base of logarithm. + * @return The logarithm of value with given base + */ + public static double log(double value, double base) { + return (Math.log(value)/Math.log(base)); + } + + + /** + * Returns a number raised to an exponent power. fBase^fExponent + * @param fBase The base value (IE 2) + * @param fExponent The exponent value (IE 3) + * @return base raised to exponent (IE 8) + * @see java.lang.Math#pow(double, double) + */ +/*! public static double pow(double fBase, double fExponent) { + return Math.pow(fBase, fExponent); + } +*/ + + /** + * Returns the value squared. fValue ^ 2 + * @param fValue The vaule to square. + * @return The square of the given value. + */ + public static double sqr(double fValue) { + return fValue * fValue; + } + + + /** + * Returns the square root of a given value. + * @param fValue The value to sqrt. + * @return The square root of the given value. + * @see java.lang.Math#sqrt(double) + */ + public static double sqrt(double fValue) { + return Math.sqrt(fValue); + } + + + /** + * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an approximate value + * is returned. Otherwise, a direct value is used. + * @param fValue The value to tangent, in radians. + * @return The tangent of fValue. + * @see java.lang.Math#tan(double) + */ + public static double tan(double fValue) { + return Math.tan(fValue); + } + + + + + + + + /** + * Returns the integral value of a given value. + * @param fValue The value to round. + * @return The square root of the given value. + * @see java.lang.Math#round(double) + */ + public static double round(double fValue) { + return Math.round(fValue); + } + + +//compute sine +/*public static double sin(double x) +{ + double aAns; + + if (x < -3.14159265) + x += 6.28318531; + else if (x > 3.14159265) + x -= 6.28318531; + + if (x < 0) + { + aAns = 1.27323954 * x + .405284735 * x * x; + + if (aAns < 0) + aAns = .225 * (aAns *-aAns - aAns) + aAns; + else + aAns = .225 * (aAns * aAns - aAns) + aAns; + } + else + { + aAns = 1.27323954 * x - 0.405284735 * x * x; + + if (aAns < 0) + aAns = .225 * (aAns *-aAns - aAns) + aAns; + else + aAns = .225 * (aAns * aAns - aAns) + aAns; + } + + return aAns; +} + + +public static double cos(double x) +{ + return sin(x + 1.57079632); +} +*/ + +//compute cosine: sin(x + PI/2) = cos(x) +/*public static double cos(double x) +{ + double aAns; + + x += 1.57079632; + if (x > 3.14159265) + x -= 6.28318531; + + if (x < 0) + { + aAns = 1.27323954 * x + 0.405284735 * x * x; + + if (aAns < 0) + aAns = .225 * (aAns *-aAns - aAns) + aAns; + else + aAns = .225 * (aAns * aAns - aAns) + aAns; + } + else + { + aAns = 1.27323954 * x - 0.405284735 * x * x; + + if (aAns < 0) + aAns = .225 * (aAns *-aAns - aAns) + aAns; + else + aAns = .225 * (aAns * aAns - aAns) + aAns; + } + + return aAns; +} +*/ + + + + +public static final double invFact2 = 1.0/2.0; +public static final double invFact3 = 1.0/6.0; +public static final double invFact4 = 1.0/24.0; +public static final double invFact5 = 1.0/120.0; +public static final double invFact6 = 1.0/720.0; +public static final double invFact7 = 1.0/5040.0; +public static final double invFact8 = 1.0/40320.0; +public static final double invFact9 = 1.0/362880.0; +public static final double invFact11 = 1.0/39916800.0; +public static final double invFact13 = 1.0/6227020800.0; +public static final double invFact15 = 1.0/1307674368000.0; + +public static final double invFact2n = -1.0/2.0; +public static final double invFact3n = -1.0/6.0; +public static final double invFact4n = -1.0/24.0; +public static final double invFact5n = -1.0/120.0; +public static final double invFact6n = -1.0/720.0; +public static final double invFact7n = -1.0/5040.0; +public static final double invFact8n = -1.0/40320.0; +public static final double invFact9n = -1.0/362880.0; +public static final double invFact11n = -1.0/39916800.0; + + +/*public static double cos(double x) +{ + double x2, x4, x6; + + while (x < -Math.PI) + x += TWO_PI; + + while (x > Math.PI) + x -= TWO_PI; + + x2 = x * x; + x4 = x2 * x2; + x6 = x4 * x2; + + return 1 - invFact2*x2 + invFact4*x4 - invFact6*x6; +}*/ +public static double cos(double x) +{ + return sin(x + HALF_PI); +} + + +public static double sin(double x) +{ +// double x2, x3, x5, x7; +// double x2, x3, x4; + double x2, x3; + + while (x < -Math.PI) + x += TWO_PI; + + while (x > Math.PI) + x -= TWO_PI; + +// x2 = x * x; +// x3 = x2 * x; +// x5 = x3 * x2; +// x7 = x5 * x2; +// return x - invFact3*x3 + invFact5*x5 - invFact7*x7; + + +// x2 = x * x; +// x3 = x2 * x; +// x4 = x3 * x; +// return x + x3*(-invFact3 + invFact5*x2 - invFact7*x4) + +// x2 = x * x; +// x3 = x2 * x; +// return x + x3*(-invFact3 + x2*(invFact5 - invFact7*x2)); + +// x2 = x * x; +// x3 = x2 * x; +// return x + x3*(-invFact3 + x2*(invFact5 + x2*(-invFact7 + x2*(invFact9 - x2*invFact11)))); + + x2 = x * x; + x3 = x2 * x; + return x + x3*(-invFact3 + x2*(invFact5 + x2*(-invFact7 + + x2*(invFact9 + x2*(-invFact11 + x2*invFact13))))); +} +//public static double sin(double x) +//{ +// return cos(x - 1.57079632679); //! This does not work +//} + + + + +/*//compute sine +public static double sin(double x) +{ + double aAns; + + if (x < -3.14159265) + x += 6.28318531; + else if (x > 3.14159265) + x -= 6.28318531; + + if (x < 0) + aAns = 1.27323954 * x + 0.405284735 * x * x; + else + aAns = 1.27323954 * x - 0.405284735 * x * x; + + return aAns; +} + + +//compute cosine: sin(x + PI/2) = cos(x) +public static double cos(double x) +{ + double aAns; + + x += 1.57079632; + if (x > 3.14159265) + x -= 6.28318531; + + if (x < 0) + aAns = 1.27323954 * x + 0.405284735 * x * x; + else + aAns = 1.27323954 * x - 0.405284735 * x * x; + + return aAns; +} +*/ + + + + + + + + + + + + + + + + /** + * pow - Method to replace Math.pow(). + * THis method should be faster but results in a coarser solution. + * Grabed from: http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ + */ + public static double pow(final double a, final double b) + { + final int x = (int) (Double.doubleToLongBits(a) >> 32); + final int y = (int) (b * (x - 1072632447) + 1072632447); + return Double.longBitsToDouble(((long) y) << 32); + } + + + + /** + * exp - Method to replace Math.exp(). + * THis method should be faster but results in a coarser solution. + * Grabed from: http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ + */ + public static double exp(double val) + { + final long tmp = (long) (1512775 * val + (1072693248 - 60801)); + return Double.longBitsToDouble(tmp << 32); + } + + + + public static double min(double aVal, double bVal) + { +// return Math.min(aVal, bVal); + if (aVal < bVal) + return aVal; + + return bVal; + } + + +} \ No newline at end of file diff --git a/src/glum/math/FractionTransformer.java b/src/glum/math/FractionTransformer.java new file mode 100644 index 0000000..1d2db02 --- /dev/null +++ b/src/glum/math/FractionTransformer.java @@ -0,0 +1,93 @@ +package glum.math; + +public enum FractionTransformer +{ + Linear("Linear", "LINEAR") + { + @Override + public double transform(double aFrac) + { + return aFrac; + } + }, + + Cosine("Cosine", "COSINE") + { + @Override + public double transform(double aFrac) + { + aFrac = aFrac * Math.PI; + aFrac = (1 - Math.cos(aFrac)) * 0.5; + + return aFrac; + } + }, + + Cubic("Cubic", "CUBIC") + { + @Override + public double transform(double aFrac) + { + double t, t2; + + t = aFrac; + t2 = aFrac * aFrac; + aFrac = 3*t2 - 2*t2*t; + + return aFrac; + } + }; + + + // Vars + private final String userStr; + private final String encodeStr; + + + /** + * Constructor + */ + FractionTransformer(String aUserStr, String aEncodeStr) + { + userStr = aUserStr; + encodeStr = aEncodeStr; + } + + + /** + * getReferenceName + */ + public String getEncodeString() + { + return encodeStr; + } + + /** + * transform + */ + public abstract double transform(double aFrac); + + /** + * parse - Returns the associated enum type + */ + public static FractionTransformer parse(String aStr) + { + if (aStr == null) + return null; + + for (FractionTransformer aEnum : FractionTransformer.values()) + { + if (aStr.equals(aEnum.getEncodeString()) == true) + return aEnum; + } + + return null; + } + + @Override + public String toString() + { + return userStr; + } + +} diff --git a/src/glum/math/GradientMesh.java b/src/glum/math/GradientMesh.java new file mode 100644 index 0000000..561c1fc --- /dev/null +++ b/src/glum/math/GradientMesh.java @@ -0,0 +1,170 @@ +package glum.math; + +import static java.lang.Math.*; +import java.util.*; + +import glum.coord.*; + +public class GradientMesh +{ + // Level of detail + int lod; + int bitMask; + + // Reference gradients + double[] grad1D; + Point2D[] grad2D; + Point3D[] grad3D; + + /** + * Constructor + */ + public GradientMesh(long aSeed, int aLod) + { + Random aRand; + + // lod: Level of detail. The number of reference gradients for + // each dimension is equal to 2^lod. Thus we support [64, 1048576] + // reference gradients. Erroneous input will be silently clamped. + lod = aLod; + if (lod < 6) + lod = 6; + else if (lod > 20) + lod = 20; + + // Compute the bit mask associated with lod + bitMask = 0; + for (int c1 = 0; c1 < lod; c1++) + bitMask = (bitMask << 1) + 1; + + // Construct our reference items + aRand = new Random(aSeed); + constructMesh1D(aRand); + constructMesh2D(aRand); +//aRand = new Random(3432); + constructMesh3D(aRand); + } + + public double getGrad1D(int randVal) + { + return grad1D[randVal & bitMask]; + } + + public Point2D getGrad2D(int randVal) + { + return grad2D[randVal & bitMask]; + } + + public Point3D getGrad3D(int randVal) + { + return grad3D[randVal & bitMask]; + } + + /** + * constructMesh1D + */ + protected void constructMesh1D(Random aRand) + { + int numItems; + + numItems = 1 << lod; + grad1D = new double[numItems]; + for (int c1 = 0; c1 < numItems; c1++) + { + if (aRand.nextDouble() < 0.5) + grad1D[c1] = -1; + else + grad1D[c1] = 1; + } + } + + /** + * constructMesh2D + */ + protected void constructMesh2D(Random aRand) + { + Point2D aPt; + double theta; + int numItems; + + numItems = 1 << lod; + grad2D = new Point2D[numItems]; + for (int c1 = 0; c1 < numItems; c1++) + { + // Compute a random point on the unit circle. + aPt = new Point2D(); + + theta = 2 * PI * aRand.nextDouble(); + aPt.x = cos(theta); + aPt.y = sin(theta); + grad2D[c1] = aPt; +/* + while (true) + { + aPt.x = 1.0 - aRand.nextDouble()*2; + aPt.z = 1.0 - aRand.nextDouble()*2; + + if (aPt.x * aPt.x + aPt.y * aPt.y <= 1.0) + break; + } + + len = sqrt((aPt.x * aPt.x) + (aPt.y * aPt.y)); + aPt.x = aPt.x / len; + aPt.y = aPt.y / len; + grad3D[c1] = aPt; +*/ + } + } + + /** + * constructMesh3D + */ + protected void constructMesh3D(Random aRand) + { + Point3D aPt; + double u, v, theta, phi; + int numItems; + + numItems = 1 << lod; + grad3D = new Point3D[numItems]; + for (int c1 = 0; c1 < numItems; c1++) + { + // Compute a random point on a sphere. The logic is taken from the site: + // http://mathworld.wolfram.com/SpherePointPicking.html + // Note you can not pick 3 points x,y, and z and then normalize them to + // the unit vector because these points are constrained by the equation: + // x^2 + y^2 + z^2 = 1 + aPt = new Point3D(); + + u = aRand.nextDouble(); + v = aRand.nextDouble(); + + theta = 2 * PI * u; + phi = acos(2*v - 1); + + // Go from phi, theta to x,y,z on the unit sphere + aPt.x = 1.0*cos(theta)*sin(phi); + aPt.y = 1.0*sin(theta)*sin(phi); + aPt.z = 1.0*cos(phi); + grad3D[c1] = aPt; +/* + while (true) + { + aPt.x = 1.0 - aRand.nextDouble()*2; + aPt.y = 1.0 - aRand.nextDouble()*2; + aPt.z = 1.0 - aRand.nextDouble()*2; + + if (aPt.x * aPt.x + aPt.y * aPt.y + aPt.z * aPt.z <= 1.0) + break; + } + + len = sqrt((aPt.x * aPt.x) + (aPt.y * aPt.y) + (aPt.z * aPt.z)); + aPt.x = aPt.x / len; + aPt.y = aPt.y / len; + aPt.z = aPt.z / len; + grad3D[c1] = aPt; +*/ + } + } + +} diff --git a/src/glum/math/GroupNoise.java b/src/glum/math/GroupNoise.java new file mode 100644 index 0000000..a975a20 --- /dev/null +++ b/src/glum/math/GroupNoise.java @@ -0,0 +1,94 @@ +package glum.math; + +import java.util.*; + +public class GroupNoise implements Noise +{ + // Collection of individual noise objects + protected Collection myNoiseList; + + /** + * Constructor + */ + public GroupNoise() + { + myNoiseList = new LinkedList(); + } + + public GroupNoise(Collection aNoiseList) + { + myNoiseList = new LinkedList(aNoiseList); + } + + /** + * Adds a Noise object to this GroupNoise + */ + public void add(Noise aNoise) + { + myNoiseList.add(aNoise); + } + + @Override + public double getAmplitude() + { + double maxAmp, aAmp; + + maxAmp = 0; + for (Noise aNoise : myNoiseList) + { + aAmp = aNoise.getAmplitude(); + if (aAmp > maxAmp) + maxAmp = aAmp; + } + + return maxAmp; + } + + /** + * getNoiseList - Returns the list of individual noises + */ + public Collection getNoiseList() + { + List aNoiseList; + + aNoiseList = new LinkedList(myNoiseList); + return aNoiseList; + } + + @Override + public double getValue1D(double x) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue1D(x); + + return total; + } + + @Override + public double getValue2D(double x, double y) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue2D(x, y); + + return total; + } + + @Override + public double getValue3D(double x, double y, double z) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue3D(x, y, z); + + return total; + } + +} diff --git a/src/glum/math/Noise.java b/src/glum/math/Noise.java new file mode 100644 index 0000000..11ecedd --- /dev/null +++ b/src/glum/math/Noise.java @@ -0,0 +1,28 @@ +package glum.math; + +public interface Noise +{ + /** + * getAmplitude - Returns the maximum offset produced by this noise + */ + public double getAmplitude(); + + /** + * getValue1D - Returns a continous random value in the range of [0-1] in 1D space. It is repeatable for the same x + * values. + */ + public double getValue1D(double x); + + /** + * getValue2D - Returns a continous random value in the range of [0-1] in 2D space. It is repeatable for the same x,y + * values. + */ + public double getValue2D(double x, double y); + + /** + * getValue3D - Returns a continous random value in the range of [0-1] in 3D space. It is repeatable for the same + * x,y,z values. + */ + public double getValue3D(double x, double y, double z); + +} diff --git a/src/glum/math/PerlinNoise.java b/src/glum/math/PerlinNoise.java new file mode 100644 index 0000000..eff99f0 --- /dev/null +++ b/src/glum/math/PerlinNoise.java @@ -0,0 +1,367 @@ +package glum.math; + +import static java.lang.Math.*; +import java.util.*; + +import glum.coord.*; + +public class PerlinNoise implements Noise +{ + // Seed key values + protected GradientMesh myGradientMesh; + protected FractionTransformer myFracTransformer; + protected double amp, freq; + + protected int[] p0Table, p1Table, p2Table, p3Table; + protected int[] p4Table, p5Table, p6Table, p7Table; + protected int[] p8Table, p9Table, p10Table, p11Table; + + /** + * Constructor + */ + public PerlinNoise(long aSeed) + { + this(aSeed, null); + } + + public PerlinNoise(long aSeed, GradientMesh aGradientMesh) + { + Vector aList; + Random aRandom; + + // Set up our rand generater + aRandom = new Random(aSeed); + + // Set up the fraction tranform and function + myFracTransformer = FractionTransformer.Cubic; + + // If no GradientMesh specified then construct our own reference gradient + // mesh with 2^12 reference points. + myGradientMesh = aGradientMesh; + if (myGradientMesh == null) + myGradientMesh = new GradientMesh(aRandom.nextLong(), 12); + + // Construct the words to use in the permutation + aList = new Vector(); + for (int c1 = 0; c1 < 256; c1++) + aList.add(c1); + + Collections.shuffle(aList, aRandom); + p0Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p0Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p1Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p1Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p2Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p2Table[c1] = aList.get(c1) << 16; + + Collections.shuffle(aList, aRandom); + p4Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p4Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p5Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p5Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p6Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p6Table[c1] = aList.get(c1) << 16; + + Collections.shuffle(aList, aRandom); + p8Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p8Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p9Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p9Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p10Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p10Table[c1] = aList.get(c1) << 16; + +/* +int x = 0xF0F0F0F0; +System.out.print(" x[0]:" + ((x & 0x000000ff) >> 0) ); +System.out.print(" x[1]:" + ((x & 0x0000ff00) >> 8) ); +System.out.print(" x[2]:" + ((x & 0x00ff0000) >> 16) ); +System.out.println(" x[3]:" + ((x & 0xff000000) >> 24) ); + +System.out.println("Hmm..."); +System.out.print(" x[0]:" + ((x >> 0) & 0xff) ); +System.out.print(" x[1]:" + ((x >> 8) & 0xff) ); +System.out.print(" x[2]:" + ((x >> 16) & 0xff) ); +System.out.println(" x[3]:" + ((x >> 24) & 0xff) ); +System.exit(0); +*/ + } + + + /** + * setPersistence - Set the amplitude and frequency associated + * with the noise function. + */ + public void setPersistence(double aAmp, double aFreq) + { + amp = aAmp; + freq = aFreq; + } + + + @Override + public double getAmplitude() + { + return amp; + } + + + + + @Override + public double getValue1D(double x) + { + return getInterpolatedValue1D(x * freq) * amp; + } + + + public double getInterpolatedValue1D(double x) + { + int iX; + double fracX; + double left, right; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + left = getSmoothValue1D(x, iX); + right = getSmoothValue1D(x, iX + 1); + + return interpolate(left, right, fracX); + } + + + public double getSmoothValue1D(double x, int qX) + { + int vx; +// int w1, w2, w3, w4; + int w1, w2, w3; + double gradPt; + double value; + + vx = qX; + w1 = (vx >> 0) & 0xFF; + w2 = (vx >> 8) & 0xFF; + w3 = (vx >> 16) & 0xFF; +// w4 = (vx >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); + + // Return a value from -1 to 1 + gradPt = myGradientMesh.getGrad1D(vx); + value = gradPt * (x - qX); + return value; + } + + + + + + + + + + + @Override + public double getValue2D(double x, double y) + { + return getInterpolatedValue2D(x * freq, y * freq) * amp; + } + + + public double getInterpolatedValue2D(double x, double y) + { + int iX, iY; + double fracX, fracY; + double tL, tR, bL, bR, topRow, botRow; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + iY = (int)floor(y); + fracY = y - iY; + fracY = myFracTransformer.transform(fracY); + + tL = getSmoothValue2D(x, y, iX, iY); + tR = getSmoothValue2D(x, y, iX + 1, iY); + bL = getSmoothValue2D(x, y, iX, iY + 1); + bR = getSmoothValue2D(x, y, iX + 1, iY + 1); + + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + + return interpolate(topRow, botRow, fracY); + } + + + public double getSmoothValue2D(double x, double y, int qX, int qY) + { + int vx, vy; +// int w1, w2, w3, w4; + int w1, w2, w3; + Point2D gradPt; + double value; + + vx = qX; + w1 = (vx >> 0) & 0xFF; + w2 = (vx >> 8) & 0xFF; + w3 = (vx >> 16) & 0xFF; +// w4 = (vx >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); + + vy = vx + qY; + w1 = (vy >> 0) & 0xFF; + w2 = (vy >> 8) & 0xFF; + w3 = (vy >> 16) & 0xFF; +// w4 = (vy >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vy = (p4Table[w1 & 0xff] ) | (p5Table[w2 & 0xff] ) | (p6Table[w3 & 0xff] ); + + // Return a value from -1 to 1 + gradPt = myGradientMesh.getGrad2D(vy); + value = (gradPt.x * (x - qX)) + (gradPt.y * (y - qY)); + return value; + } + + + + + + + + + + + + + + @Override + public double getValue3D(double x, double y, double z) + { + return getInterpolatedValue3D(x * freq, y * freq, z * freq) * amp; + } + + + public double getInterpolatedValue3D(double x, double y, double z) + { + int iX, iY, iZ; + double fracX, fracY, fracZ; + double tL, tR, bL, bR, topRow, botRow; + double planeZ1, planeZ2; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + iY = (int)floor(y); + fracY = y - iY; + fracY = myFracTransformer.transform(fracY); + + iZ = (int)floor(z); + fracZ = z - iZ; + fracZ = myFracTransformer.transform(fracZ); + + tL = getSmoothValue3D(x, y, z, iX, iY, iZ); + tR = getSmoothValue3D(x, y, z, iX + 1, iY, iZ); + bL = getSmoothValue3D(x, y, z, iX, iY + 1, iZ); + bR = getSmoothValue3D(x, y, z, iX + 1, iY + 1, iZ); + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + planeZ1 = interpolate(topRow, botRow, fracY); + + tL = getSmoothValue3D(x, y, z, iX, iY, iZ + 1); + tR = getSmoothValue3D(x, y, z, iX + 1, iY, iZ + 1); + bL = getSmoothValue3D(x, y, z, iX, iY + 1, iZ + 1); + bR = getSmoothValue3D(x, y, z, iX + 1, iY + 1, iZ + 1); + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + planeZ2 = interpolate(topRow, botRow, fracY); + + return interpolate(planeZ1, planeZ2, fracZ); + } + + + public double getSmoothValue3D(double x, double y, double z, int qX, int qY, int qZ) + { + int vx, vy, vz; +// int w1, w2, w3, w4; + int w1, w2, w3; + Point3D gradPt; + double value; + + vx = qX; + w1 = (vx >> 0) & 0xFF; + w2 = (vx >> 8) & 0xFF; + w3 = (vx >> 16) & 0xFF; +// w4 = (vx >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); + + vy = vx + qY; + w1 = (vy >> 0) & 0xFF; + w2 = (vy >> 8) & 0xFF; + w3 = (vy >> 16) & 0xFF; +// w4 = (vy >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vy = (p4Table[w1 & 0xff] ) | (p5Table[w2 & 0xff] ) | (p6Table[w3 & 0xff] ); + + vz = vy + qZ; + w1 = (vz >> 0) & 0xFF; + w2 = (vz >> 8) & 0xFF; + w3 = (vz >> 16) & 0xFF; +// w4 = (vz >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vz = (p8Table[w1 & 0xff] ) | (p9Table[w2 & 0xff] ) | (p10Table[w3 & 0xff] ); + + // Return a value from -1 to 1 + gradPt = myGradientMesh.getGrad3D(vz); + value = (gradPt.x * (x - qX)) + (gradPt.y * (y - qY)) + (gradPt.z * (z - qZ)); + return value; + } + + + /** + * interpolate - Returns a value between v1 and v2 wrt to frac. + */ + protected double interpolate(double v1, double v2, double frac) + { + return v1 + frac*(v2 - v1); + } + +} + diff --git a/src/glum/math/TransformNoise.java b/src/glum/math/TransformNoise.java new file mode 100644 index 0000000..2c840fb --- /dev/null +++ b/src/glum/math/TransformNoise.java @@ -0,0 +1,44 @@ +package glum.math; + +public class TransformNoise implements Noise +{ + // State vars + protected Noise refNoise; + protected double offsetVal; + protected double scalarVal; + + /** + * Constructor + */ + public TransformNoise(Noise aRefNoise, double aOffsetVal, double aScalarVal) + { + refNoise = aRefNoise; + offsetVal = aOffsetVal; + scalarVal = aScalarVal; + } + + @Override + public double getAmplitude() + { + return scalarVal * refNoise.getAmplitude(); + } + + @Override + public double getValue1D(double x) + { + return offsetVal + (scalarVal * refNoise.getValue1D(x)); + } + + @Override + public double getValue2D(double x, double y) + { + return offsetVal + (scalarVal * refNoise.getValue2D(x, y)); + } + + @Override + public double getValue3D(double x, double y, double z) + { + return offsetVal + (scalarVal * refNoise.getValue3D(x, y, z)); + } + +} diff --git a/src/glum/math/TurbulentNoise.java b/src/glum/math/TurbulentNoise.java new file mode 100644 index 0000000..9f21a38 --- /dev/null +++ b/src/glum/math/TurbulentNoise.java @@ -0,0 +1,40 @@ +package glum.math; + +public class TurbulentNoise implements Noise +{ + // State vars + protected Noise refNoise; + + /** + * Constructor + */ + public TurbulentNoise(Noise aRefNoise) + { + refNoise = aRefNoise; + } + + @Override + public double getAmplitude() + { + return refNoise.getAmplitude(); + } + + @Override + public double getValue1D(double x) + { + return Math.abs(refNoise.getValue1D(x)); + } + + @Override + public double getValue2D(double x, double y) + { + return Math.abs(refNoise.getValue2D(x, y)); + } + + @Override + public double getValue3D(double x, double y, double z) + { + return Math.abs(refNoise.getValue3D(x, y, z)); + } + +} diff --git a/src/glum/math/ValueNoise.java b/src/glum/math/ValueNoise.java new file mode 100644 index 0000000..7f1f64f --- /dev/null +++ b/src/glum/math/ValueNoise.java @@ -0,0 +1,422 @@ +package glum.math; + +import static java.lang.Math.*; +import java.util.*; + + +public class ValueNoise implements Noise +{ + // Seed key values + protected FractionTransformer myFracTransformer; + protected double amp, freq; + protected int p0, p1, p2, p3, p4; + protected int[] p0Table, p1Table, p2Table; + protected int[] p4Table, p5Table, p6Table, p7Table; + protected int[] p8Table, p9Table, p10Table, p11Table; + + /** + * Constructor + */ + public ValueNoise(long aSeed) + { + p0 = 57; + p1 = 15731; + p2 = 789221; + p3 = 1376312589; + + Vector aList; + Random aRandom; + + // Set up our rand generater + aRandom = new Random(aSeed); + + // Set up the fraction tranform function + myFracTransformer = FractionTransformer.Cubic; + + // Construct the words to use in the permutation + aList = new Vector(); + for (int c1 = 0; c1 < 256; c1++) + aList.add(c1); + + Collections.shuffle(aList, aRandom); + p0Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p0Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p1Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p1Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p2Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p2Table[c1] = aList.get(c1) << 16; + + Collections.shuffle(aList, aRandom); + p4Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p4Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p5Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p5Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p6Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p6Table[c1] = aList.get(c1) << 16; + + Collections.shuffle(aList, aRandom); + p8Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p8Table[c1] = aList.get(c1) << 0; + + Collections.shuffle(aList, aRandom); + p9Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p9Table[c1] = aList.get(c1) << 8; + + Collections.shuffle(aList, aRandom); + p10Table = new int[256]; + for (int c1 = 0; c1 < 256; c1++) + p10Table[c1] = aList.get(c1) << 16; + +/* +int x = 0xF0F0F0F0; +System.out.print(" x[0]:" + ((x & 0x000000ff) >> 0) ); +System.out.print(" x[1]:" + ((x & 0x0000ff00) >> 8) ); +System.out.print(" x[2]:" + ((x & 0x00ff0000) >> 16) ); +System.out.println(" x[3]:" + ((x & 0xff000000) >> 24) ); + +System.out.println("Hmm..."); +System.out.print(" x[0]:" + ((x >> 0) & 0xff) ); +System.out.print(" x[1]:" + ((x >> 8) & 0xff) ); +System.out.print(" x[2]:" + ((x >> 16) & 0xff) ); +System.out.println(" x[3]:" + ((x >> 24) & 0xff) ); +System.exit(0); +*/ + } + + public ValueNoise(int aP0, int aP1, int aP2, int aP3) + { + this(0); + + amp = 1.0; + freq = 1.0; + p0 = aP0; + p1 = aP1; + p2 = aP2; + p3 = aP3; + } + + + /** + * setPersistence - Set the amplitude and frequency associated + * with the noise function. + */ + public void setPersistence(double aAmp, double aFreq) + { + amp = aAmp; + freq = aFreq; + } + + + @Override + public double getAmplitude() + { + return amp; + } + + + @Override + public double getValue1D(double x) + { + return getInterpolatedValue1D(x * freq) * amp; + } + + + public double getInterpolatedValue1D(double x) + { + int iX; + double fracX; + double left, right; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + left = getSmoothValue1D(iX); + right = getSmoothValue1D(iX + 1); + + return interpolate(left, right, fracX); + } + + + public double getSmoothValue1D(int x) + { + double corners, center; + + corners = (getRawValue1D(x-1) + getRawValue1D(x+1)) / 2; + center = getRawValue1D(x) / 2; + return corners + center; + } + + + public double getRawValue1D(int x) + { + int vx; +// int w1, w2, w3, w4; + int w1, w2, w3; + +/* long aVal; + + aVal = x + (y * p0); + aVal = (aVal <<13) ^ aVal; + + return (1.0 - ((aVal * (aVal * aVal * p1 + p2) + p3) & 0x7fffffff) / 1073741824.0); +*/ + + vx = x; + w1 = (vx >> 0) & 0xFF; + w2 = (vx >> 8) & 0xFF; + w3 = (vx >> 16) & 0xFF; +// w4 = (vx >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); + + // Return a value from -1 to 1. The value of vy is in the range: [0, 2^24 - 1] + return 1.0 - (vx/8388607.5); + } + + + + + + + + + + + @Override + public double getValue2D(double x, double y) + { + return getInterpolatedValue2D(x * freq, y * freq) * amp; + } + + + public double getInterpolatedValue2D(double x, double y) + { + int iX, iY; + double fracX, fracY; + double tL, tR, bL, bR, topRow, botRow; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + iY = (int)floor(y); + fracY = y - iY; + fracY = myFracTransformer.transform(fracY); + + tL = getSmoothValue2D(iX, iY); + tR = getSmoothValue2D(iX + 1, iY); + bL = getSmoothValue2D(iX, iY + 1); + bR = getSmoothValue2D(iX + 1, iY + 1); + + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + + return interpolate(topRow, botRow, fracY); + } + + + public double getSmoothValue2D(int x, int y) + { +return getRawValue2D(x, y); +/* double corners, sides, center; + + corners = (getRawValue2D(x-1, y-1) + getRawValue2D(x+1, y-1) + getRawValue2D(x-1, y+1) + getRawValue2D(x+1, y+1)) / 16; + sides = (getRawValue2D(x-1, y) + getRawValue2D(x+1, y) + getRawValue2D(x, y-1) + getRawValue2D(x, y+1)) / 8; + center = getRawValue2D(x, y) / 4; + + return corners + sides + center; +*/ } + + + public double getRawValue2D(int x, int y) + { +// int vx, vy; +// int w1, w2, w3, w4; + + long aVal; + aVal = x + (y * p0); + aVal = (aVal <<13) ^ aVal; + + return (1.0 - ((aVal * (aVal * aVal * p1 + p2) + p3) & 0x7fffffff) / 1073741824.0); + + +// vx = x; +// w1 = (vx >> 0) & 0xFF; +// w2 = (vx >> 8) & 0xFF; +// w3 = (vx >> 16) & 0xFF; +//// w4 = (vx >> 24) & 0xFF; +// w2 = w2 + w1; +// w3 = w3 + w2; +//// w4 = w4 + w3; +// vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); +// +// vy = vx + y; +// w1 = (vy >> 0) & 0xFF; +// w2 = (vy >> 8) & 0xFF; +// w3 = (vy >> 16) & 0xFF; +//// w4 = (vy >> 24) & 0xFF; +// w2 = w2 + w1; +// w3 = w3 + w2; +//// w4 = w4 + w3; +// vy = (p4Table[w1 & 0xff] ) | (p5Table[w2 & 0xff] ) | (p6Table[w3 & 0xff] ); +// +// // Return a value from -1 to 1. The value of vy is in the range: [0, 2^24 - 1] +// return 1.0 - (vy/8388607.5); + } + + + + + + + + + + + + + + @Override + public double getValue3D(double x, double y, double z) + { + return getInterpolatedValue3D(x * freq, y * freq, z * freq) * amp; + } + + + public double getInterpolatedValue3D(double x, double y, double z) + { + int iX, iY, iZ; + double fracX, fracY, fracZ; + double tL, tR, bL, bR, topRow, botRow; + double planeZ1, planeZ2; + + iX = (int)floor(x); + fracX = x - iX; + fracX = myFracTransformer.transform(fracX); + + iY = (int)floor(y); + fracY = y - iY; + fracY = myFracTransformer.transform(fracY); + + iZ = (int)floor(z); + fracZ = z - iZ; + fracZ = myFracTransformer.transform(fracZ); + + tL = getSmoothValue3D(iX, iY, iZ); + tR = getSmoothValue3D(iX + 1, iY, iZ); + bL = getSmoothValue3D(iX, iY + 1, iZ); + bR = getSmoothValue3D(iX + 1, iY + 1, iZ); + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + planeZ1 = interpolate(topRow, botRow, fracY); + + tL = getSmoothValue3D(iX, iY, iZ + 1); + tR = getSmoothValue3D(iX + 1, iY, iZ + 1); + bL = getSmoothValue3D(iX, iY + 1, iZ + 1); + bR = getSmoothValue3D(iX + 1, iY + 1, iZ + 1); + topRow = interpolate(tL, tR, fracX); + botRow = interpolate(bL, bR, fracX); + planeZ2 = interpolate(topRow, botRow, fracY); + + return interpolate(planeZ1, planeZ2, fracZ); + } + + + public double getSmoothValue3D(int x, int y, int z) + { +return getRawValue3D(x, y, z); +/* double center, surface, side, corner; + + center = getRawValue3D(x, y, z); + center = center / 8; + + surface = getRawValue3D(x - 1, y, z) + getRawValue3D(x + 1, y, z) + + getRawValue3D(x, y - 1, z) + getRawValue3D(x, y + 1, z) + + getRawValue3D(x, y, z - 1) + getRawValue3D(x, y, z + 1); + surface = surface / 16; + + side = getRawValue3D(x, y-1, z-1) + getRawValue3D(x, y-1, z+1) + getRawValue3D(x, y+1, z-1) + getRawValue3D(x, y+1, z+1) + + getRawValue3D(x-1, y, z-1) + getRawValue3D(x-1, y, z+1) + getRawValue3D(x+1, y, z-1) + getRawValue3D(x+1, y, z+1) + + getRawValue3D(x-1, y-1, z) + getRawValue3D(x-1, y+1, z) + getRawValue3D(x+1, y-1, z) + getRawValue3D(x+1, y+1, z); + side = side / 32; + + corner = getRawValue3D(x-1, y-1, z-1) + getRawValue3D(x+1, y-1, z-1) + + getRawValue3D(x-1, y+1, z-1) + getRawValue3D(x+1, y+1, z-1) + + getRawValue3D(x-1, y-1, z+1) + getRawValue3D(x+1, y-1, z+1) + + getRawValue3D(x-1, y+1, z+1) + getRawValue3D(x+1, y+1, z+1); + corner = corner / 64; + + return center + surface + side + corner; +*/ } + + + public double getRawValue3D(int x, int y, int z) + { + int vx, vy, vz; +// int w1, w2, w3, w4; + int w1, w2, w3; + + vx = x; + w1 = (vx >> 0) & 0xFF; + w2 = (vx >> 8) & 0xFF; + w3 = (vx >> 16) & 0xFF; +// w4 = (vx >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vx = (p0Table[w1 & 0xff] ) | (p1Table[w2 & 0xff] ) | (p2Table[w3 & 0xff] ); + + vy = vx + y; + w1 = (vy >> 0) & 0xFF; + w2 = (vy >> 8) & 0xFF; + w3 = (vy >> 16) & 0xFF; +// w4 = (vy >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vy = (p4Table[w1 & 0xff] ) | (p5Table[w2 & 0xff] ) | (p6Table[w3 & 0xff] ); + + vz = vy + z; + w1 = (vz >> 0) & 0xFF; + w2 = (vz >> 8) & 0xFF; + w3 = (vz >> 16) & 0xFF; +// w4 = (vz >> 24) & 0xFF; + w2 = w2 + w1; + w3 = w3 + w2; +// w4 = w4 + w3; + vz = (p8Table[w1 & 0xff] ) | (p9Table[w2 & 0xff] ) | (p10Table[w3 & 0xff] ); + + // Return a value from -1 to 1. The value of vy is in the range: [0, 2^24 - 1] + return 1.0 - (vz/8388607.5); + } + + + /** + * interpolate - Returns a value between v1 and v2 wrt to frac. + */ + protected double interpolate(double v1, double v2, double frac) + { + return v1 + frac*(v2 - v1); + } + +} + diff --git a/src/glum/math/ViolentNoise1.java.misc b/src/glum/math/ViolentNoise1.java.misc new file mode 100644 index 0000000..8b3f2a3 --- /dev/null +++ b/src/glum/math/ViolentNoise1.java.misc @@ -0,0 +1,79 @@ +package glum.math; + +import java.util.*; + +public class ViolentNoise1 implements Noise +{ + // Seed key values + protected Collection myNoiseList; + + + /** + * Constructor + */ + public ViolentNoise1() + { + myNoiseList = new LinkedList(); + } + + + /** + * add + */ + public void add(Noise aNoise) + { + myNoiseList.add(aNoise); + } + + + /** + * getValue1D - Returns a continous random value in the range of [0-1] in + * 1D space. It is repeatable for the same x values. + */ + public double getValue1D(double x) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue1D(x); + + total = Math.sin(x + total); + return total; + } + + + /** + * getValue2D - Returns a continous random value in the range of [0-1] in + * 2D space. It is repeatable for the same x,y values. + */ + public double getValue2D(double x, double y) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue2D(x, y); + + total = Math.sin(x + total); + return total; + } + + + /** + * getValue3D - Returns a continous random value in the range of [0-1] in + * 3D space. It is repeatable for the same x,y,z values. + */ + public double getValue3D(double x, double y, double z) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += aNoiseGen.getValue3D(x, y, z); + + total = Math.sin(x + total); + return total; + } + +} diff --git a/src/glum/math/ViolentNoise2.java.misc b/src/glum/math/ViolentNoise2.java.misc new file mode 100644 index 0000000..a2c75ec --- /dev/null +++ b/src/glum/math/ViolentNoise2.java.misc @@ -0,0 +1,79 @@ +package glum.math; + +import java.util.*; + +public class ViolentNoise2 implements Noise +{ + // Seed key values + protected Collection myNoiseList; + + + /** + * Constructor + */ + public ViolentNoise2() + { + myNoiseList = new LinkedList(); + } + + + /** + * add + */ + public void add(Noise aNoise) + { + myNoiseList.add(aNoise); + } + + + /** + * getValue1D - Returns a continous random value in the range of [0-1] in + * 1D space. It is repeatable for the same x values. + */ + public double getValue1D(double x) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += Math.abs(aNoiseGen.getValue1D(x)); + + total = Math.sin(x + total); + return total; + } + + + /** + * getValue2D - Returns a continous random value in the range of [0-1] in + * 2D space. It is repeatable for the same x,y values. + */ + public double getValue2D(double x, double y) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += Math.abs(aNoiseGen.getValue2D(x, y)); + + total = Math.sin(x + total); + return total; + } + + + /** + * getValue3D - Returns a continous random value in the range of [0-1] in + * 3D space. It is repeatable for the same x,y,z values. + */ + public double getValue3D(double x, double y, double z) + { + double total; + + total = 0; + for (Noise aNoiseGen : myNoiseList) + total += Math.abs(aNoiseGen.getValue3D(x, y, z)); + + total = Math.tan(x + total); + return total; + } + +} diff --git a/src/glum/net/Credential.java b/src/glum/net/Credential.java new file mode 100644 index 0000000..1e6d94d --- /dev/null +++ b/src/glum/net/Credential.java @@ -0,0 +1,64 @@ +package glum.net; + +public class Credential +{ + // Constants + public static final Credential NONE = new Credential("", new char[0]); + + // State vars + protected final String domain; + protected final String username; + protected final char[] password; + + /** + * Creates a domainless credential. + * + * @param aUsername + * @param aPassword + * the password to utilize. Note: a reference to this memory is retained by design to minimize distribution + * of the password content through the application. + */ + public Credential(String aUsername, char[] aPassword) + { + domain = ""; + username = aUsername; + password = aPassword; + } + + public void dispose() + { + for (int c1 = 0; c1 < password.length; c1++) + password[c1] = 0; + } + + public String getDomain() + { + return domain; + } + + public String getUsername() + { + return username; + } + + /** + * Returns the password character array. + * + * @return a reference to the internal array held by the instance. + */ + public char[] getPassword() + { + return password; + } + + public String getPasswordAsString() + { + if (password == null) + return ""; + + // TODO This is a big no no. The password should always be stored as a + // character array and zeroed out before this object is garbage collected. + return new String(password); + } + +} diff --git a/src/glum/net/NetUtil.java b/src/glum/net/NetUtil.java new file mode 100644 index 0000000..cae22d4 --- /dev/null +++ b/src/glum/net/NetUtil.java @@ -0,0 +1,153 @@ +package glum.net; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; + +import javax.xml.bind.DatatypeConverter; + +public class NetUtil +{ + /** + * Utility method for retrieving the response code + */ + public static int getResponseCode(HttpURLConnection aConnection) + { + try + { + return aConnection.getResponseCode(); + } + catch (IOException aExp) + { + return -1; + } + } + + /** + * Utility method to return the input stream associated with a URL + */ + public static InputStream getInputStream(URLConnection aConnection, Credential aCredential) throws IOException + { + InputStream inStream; + String authStr; + + // Properly setup the credentials + if (aCredential != null) + { + authStr = aCredential.getUsername() + ":" + aCredential.getPasswordAsString(); + authStr = DatatypeConverter.printBase64Binary(authStr.getBytes()); + aConnection.setRequestProperty("Authorization", "Basic " + authStr); + } + + // Retrieve the InputStream + aConnection.connect(); + inStream = aConnection.getInputStream(); + + return inStream; + } + + /** + * Utility method to return the input stream associated with a URL + */ + public static InputStream getInputStream(URL aUrl, Credential aCredential) throws IOException + { + return getInputStream(aUrl.openConnection(), aCredential); + } + + /** + * Utility method for retrieving a more detailed result associated with an exception that occured on a URLConnection + */ + public static Result getResult(Exception aExp, URLConnection aConnection) + { + Throwable aCause; + + // See if there was a problem with the HTTP Connection + if (aConnection instanceof HttpURLConnection) + { + int responseCode; + + responseCode = getResponseCode((HttpURLConnection)aConnection); + switch (responseCode) + { + case HttpURLConnection.HTTP_UNAUTHORIZED: + return Result.BadCredentials; + + case HttpURLConnection.HTTP_UNSUPPORTED_TYPE: + return Result.UnsupportedConnection; + + case HttpURLConnection.HTTP_NOT_FOUND: + case HttpURLConnection.HTTP_NO_CONTENT: + return Result.InvalidResource; + +// case HttpURLConnection.HTTP_UNAVAILABLE: +// return Result.UnreachableHost; + + default: + break; + } + } + + // Evaluate the Exception + aCause = aExp; + while (aCause != null) + { + if (aCause instanceof UnknownHostException) + return Result.UnreachableHost; + else if (aCause instanceof ConnectException) + return Result.ConnectFailure; + + aCause = aCause.getCause(); + } + + return Result.Undefined; + + } + + /** + * Checks to see whether the supplied aCredential is valid for the specified root URI. + * + * @return The result of testing the credentials. + */ + public static Result checkCredentials(String uriRoot, Credential aCredential) + { + URLConnection aConnection; + InputStream inStream; + String username, password; + String authStr; + URL srcURL; +// int fullLen; + + aConnection = null; + inStream = null; + try + { + srcURL = new URL(uriRoot); + + username = aCredential.getUsername(); + password = aCredential.getPasswordAsString(); + authStr = username + ":" + password; + authStr = DatatypeConverter.printBase64Binary(authStr.getBytes()); +// authStr = new sun.misc.BASE64Encoder().encode((username + ":" + password).getBytes()); + aConnection = srcURL.openConnection(); + aConnection.setRequestProperty("Authorization", "Basic " + authStr); + aConnection.connect(); + + // Try to open the connection +// fullLen = aConnection.getContentLength(); + inStream = new BufferedInputStream(aConnection.getInputStream()); + inStream.close(); + return Result.Success; + } + catch (Exception aExp) + { +// aExp.printStackTrace(); + return getResult(aExp, aConnection); + } + } + +} diff --git a/src/glum/net/Result.java b/src/glum/net/Result.java new file mode 100644 index 0000000..c59da4e --- /dev/null +++ b/src/glum/net/Result.java @@ -0,0 +1,13 @@ +package glum.net; + +public enum Result +{ + UnreachableHost, + ConnectFailure, + UnsupportedConnection, + BadCredentials, + InvalidResource, + Interrupted, + Undefined, + Success, +} diff --git a/src/glum/reflect/Function.java b/src/glum/reflect/Function.java new file mode 100644 index 0000000..31f56fd --- /dev/null +++ b/src/glum/reflect/Function.java @@ -0,0 +1,64 @@ +package glum.reflect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Class to provide easy access to function and allow them to be manipulated as objects. + *

    + * Source: http://stackoverflow.com/questions/1073358/function-pointers-in-java
    + * Source: http://tutorials.jenkov.com/java-reflection/private-fields-and-methods.html
    + * Source: http://java.sun.com/developer/technicalArticles/ALT/Reflection/ + */ +public class Function +{ + private Object refObject; + private Method refMethod; + + public Function(Method m) + { + refObject = null; + refMethod = m; + } + + public Function(Object aObject, Method aMethod) + { + refObject = aObject; + refMethod = aMethod; + } + + public Function(Class aClass, String methodName, Class... methodParameterTypes) throws NoSuchMethodException + { + refObject = null; + + // Locate the appropriate method + refMethod = ReflectUtil.locateMatchingMethod(aClass, methodName, methodParameterTypes); + if (refMethod == null) + throw new NoSuchMethodException("Failed to locate named: " + methodName + " with parameters: " + + ReflectUtil.getStringForClassArray(methodParameterTypes)); + + refMethod.setAccessible(true); + } + + public Function(Object aObj, String methodName, Class... methodParameterTypes) throws NoSuchMethodException + { + refObject = aObj; + + // Locate the appropriate method + refMethod = ReflectUtil.locateMatchingMethod(aObj.getClass(), methodName, methodParameterTypes); + if (refMethod == null) + throw new NoSuchMethodException("Failed to locate named: " + methodName + " with parameters: " + + ReflectUtil.getStringForClassArray(methodParameterTypes)); + + refMethod.setAccessible(true); + } + + /** + * Method used to called the function associated with this object. The parameters are passed in via argsArr + */ + public Object invoke(Object... argsArr) throws IllegalAccessException, InvocationTargetException + { + return refMethod.invoke(refObject, argsArr); + } + +}; diff --git a/src/glum/reflect/FunctionRunnable.java b/src/glum/reflect/FunctionRunnable.java new file mode 100644 index 0000000..20ba6e7 --- /dev/null +++ b/src/glum/reflect/FunctionRunnable.java @@ -0,0 +1,67 @@ +package glum.reflect; + +/** + * Class that wraps the provided function in a Runnable + */ +public class FunctionRunnable implements Runnable +{ + protected Function refFunc; + protected Object[] argArr; + + public FunctionRunnable(Object aObj, String methodName, Object... aArgArr) + { + Class[] typeArr; + + argArr = aArgArr; + + typeArr = new Class[0]; + if (argArr.length > 0) + { + // Determine the types of the specified arguments + typeArr = new Class[argArr.length]; + for (int c1 = 0; c1 < typeArr.length; c1++) + { + typeArr[c1] = null; + if (argArr[c1] != null) + typeArr[c1] = argArr[c1].getClass(); + } + } + + try + { + if (aObj instanceof Class) + refFunc = new Function((Class)aObj, methodName, typeArr); + else + refFunc = new Function(aObj, methodName, typeArr); + } + catch (Exception aExp) + { + String aMsg; + + aMsg = "Failed to locate valid function. Method:" + methodName; + aMsg += "\n Class:" + aObj.getClass(); + throw new RuntimeException(aMsg, aExp); + } + } + + @Override + public void run() + { + try + { + refFunc.invoke(argArr); + } + catch (Exception aExp) + { + Throwable aCause; + + // Retrieve the root cause + aCause = aExp; + while (aCause.getCause() != null) + aCause = aCause.getCause(); + + throw new RuntimeException("Failed to invoke method.", aCause); + } + } + +} diff --git a/src/glum/reflect/ReflectUtil.java b/src/glum/reflect/ReflectUtil.java new file mode 100644 index 0000000..9e85707 --- /dev/null +++ b/src/glum/reflect/ReflectUtil.java @@ -0,0 +1,292 @@ +package glum.reflect; + +import java.io.File; +import java.lang.reflect.*; +import java.net.URL; +import java.net.URLDecoder; + +import javax.lang.model.type.NullType; + +import com.google.common.primitives.Primitives; + +public class ReflectUtil +{ + /** + * Utility method to check if an object of type rightType can be assigned to leftType + *

    + * If the leftType is a non primitive then rightType must be null or of a child class. + *

    + * If the leftType is a primitive then this method will only return true if the rightType is a primitive and does not + * need to be cast so that loss of information will occur. I.E:
    + * checkAssignability(long, Int) -> true
    + * checkAssignability(int, Long) -> false + */ + static boolean checkAssignability(Class leftType, Class rightType) + { + // Evaluate non primitive assignments + if (leftType.isPrimitive() == false) + { + if (rightType == null || rightType == NullType.class) + return true; + else + return leftType.isAssignableFrom(rightType); + } + + // Evaluate primitive assignments + if (rightType == null) + return false; + + leftType = Primitives.wrap(leftType); + rightType = Primitives.wrap(rightType); + + // Bail if same types + if (leftType == rightType) + return true; + + if (leftType == Double.class) + { + if (rightType == Float.class) + return true; + } + else if (leftType == Long.class) + { + if (rightType == Integer.class || rightType == Short.class || rightType == Character.TYPE || rightType == Byte.class) + return true; + } + else if (leftType == Integer.class) + { + if (rightType == Short.class || rightType == Character.TYPE || rightType == Byte.class) + return true; + } + else if (leftType == Short.class) + { + if (rightType == Character.TYPE || rightType == Byte.class) + return true; + } + + return false; + } + + /** + * Returns the Constructor of aClass with arguments matching parmTypes. On failure it returns null rather than throw + * an exception. + */ + public static Constructor getConstructorSafe(Class aClass, Class... parmTypes) + { + Constructor aConstructor; + + try + { + aConstructor = aClass.getConstructor(parmTypes); + } + catch (Exception aExp) + { + return null; + } + + return aConstructor; + } + + /** + * Returns a neatly formatted string corresponding to classArr + */ + public static String getStringForClassArray(Class... classArr) + { + String retStr; + + retStr = "["; + for (int c1 = 0; c1 < classArr.length; c1++) + { + if (classArr[c1] == null) + retStr += "null"; + else + retStr += classArr[c1].getName(); + + if (c1 + 1 != classArr.length) + retStr += ", "; + } + retStr += "]"; + return retStr; + + } + + /** + * Attempts to return an object of type G1 from the fully qualified path + */ + public static G1 loadObject(String aFullClassPath, Class retType) + { + Class aClass; + Object aObject; + + // Insanity check + if (aFullClassPath == null) + return null; + + aObject = null; + try + { + aClass = Class.forName(aFullClassPath); + if (retType.isAssignableFrom(aClass) == false) + return null; + + aObject = aClass.newInstance(); + return retType.cast(aObject); + } + catch (ClassNotFoundException aExp) + { + System.out.println("Failure: " + aFullClassPath + " not found."); + } + catch (Exception aExp) + { + // Unknown Exception + aExp.printStackTrace(); + } + + return null; + } + + /** + * Attempts to return an object of type G1 from the fully qualified path. The object will be constructed using the + * vars parmTypes and parmValues. + */ + public static G1 loadObject(String aFullClassPath, Class retType, Class[] parmTypes, Object[] parmValues) + { + Class aClass; + Constructor aConstructor; + Object aObject; + + // Insanity check + if (aFullClassPath == null) + return null; + + aObject = null; + try + { + aClass = Class.forName(aFullClassPath); + if (retType.isAssignableFrom(aClass) == false) + return null; + + // Try to obtain the preferred constructor + aConstructor = aClass.getConstructor(parmTypes); + if (aConstructor == null) + return null; + + // Construct the object + aObject = aConstructor.newInstance(parmValues); + return retType.cast(aObject); + } + catch (ClassNotFoundException aExp) + { + System.out.println("Failure: " + aFullClassPath + " not found."); + } + catch (Exception aExp) + { + // Unknown Exception + aExp.printStackTrace(); + } + + return null; + } + + /** + * Utility method to determine where refClass is installed + */ + public static File getInstalledRootDir(Class refClass) + { + String dataPath; + File rootDir; + URL aUrl; + int aIndex; + + // Attempt to determine the default data directory + aUrl = refClass.getResource(refClass.getSimpleName() + ".class"); + // System.out.println("URL:" + aUrl); + try + { + dataPath = aUrl.toURI().toString(); + dataPath = URLDecoder.decode(dataPath, "UTF-8"); + } + catch (Exception aExp) + { + dataPath = aUrl.getPath(); + try + { + dataPath = URLDecoder.decode(dataPath, "UTF-8"); + } + catch (Exception aExp2) + { + ; + } + } + + // Remove the "file:" protocol specification + aIndex = dataPath.indexOf("file:"); + if (aIndex != -1) + dataPath = dataPath.substring(aIndex + 5); + else + System.out.println("Warning: Protocol \"file:\" not found. Run from http???"); + + // Remove the jar file component from the path "!*.jar" + aIndex = dataPath.lastIndexOf("!"); + if (aIndex != -1) + { + dataPath = dataPath.substring(0, aIndex); + rootDir = new File(dataPath); + rootDir = rootDir.getParentFile(); + } + else + { + System.out.println("Warning: " + refClass.getSimpleName() + " class does not appear to be packaged. Assuming developers environment."); + rootDir = new File(dataPath); + rootDir = rootDir.getParentFile().getParentFile().getParentFile(); + } + + return rootDir; + } + + /** + * Utility method that searches for a matching method from the specified class, refClass. If null is specified as any + * of the parameter types, then any non primitive object will be considered a match for that parameter. + */ + public static Method locateMatchingMethod(Class refClass, String methodName, Class... methodParamTypeArr) + { + Method[] methodArr; + boolean isMatch; + Class aClass; + + // Search all of the available methods + methodArr = refClass.getDeclaredMethods(); + for (Method aItem : methodArr) + { + // Ensure the name matches + if (methodName.equals(aItem.getName()) == true) + { + Class[] evalParamTypeArr; + + // Ensure the number of arguments matches + evalParamTypeArr = aItem.getParameterTypes(); + if (evalParamTypeArr.length == methodParamTypeArr.length) + { + isMatch = true; + for (int c1 = 0; c1 < evalParamTypeArr.length; c1++) + { + isMatch &= checkAssignability(evalParamTypeArr[c1], methodParamTypeArr[c1]); + } + + // Found a matching method + if (isMatch == true) + return aItem; + } + } + } + + // Try the super classes + aClass = refClass.getSuperclass(); + if (aClass != null) + return locateMatchingMethod(aClass, methodName, methodParamTypeArr); + + // No method found + return null; + } + +} diff --git a/src/glum/registry/ConfigMap.java b/src/glum/registry/ConfigMap.java new file mode 100644 index 0000000..5e02358 --- /dev/null +++ b/src/glum/registry/ConfigMap.java @@ -0,0 +1,212 @@ +package glum.registry; + +import java.util.*; + +/** + * ConfigMap is a general purpose mapping allowing objects to be linked together by a key. On retrievals of objects from + * the ConfigMap - a default can be specified should no mapping be found. + */ +public class ConfigMap +{ + // State var + private Map> mySetMap; + private Map mySingletonMap; + + public ConfigMap() + { + mySetMap = new LinkedHashMap>(); + mySingletonMap = new LinkedHashMap(); + } + + /** + * Adds aObject to the appropriate collection w.r.t aKey + */ + public synchronized void addItem(Object aKey, Object aObject) + { + Collection aCollection; + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + { + aCollection = new LinkedHashSet(); + mySetMap.put(aKey, aCollection); + } + aCollection.add(aObject); + } + + /** + * Adds a collection of objects to the appropriate collection w.r.t aKey + */ + public synchronized void addItems(Object aKey, Collection aObjectList) + { + Collection aCollection; + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + { + aCollection = new LinkedHashSet(); + mySetMap.put(aKey, aCollection); + } + aCollection.addAll(aObjectList); + } + + /** + * Returns the appropriate collection w.r.t aKey + */ + public synchronized Collection getItems(Object aKey) + { + Collection aCollection; + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return new ArrayList(); + + return new ArrayList(aCollection); + } + + public synchronized Collection getItems(Object aKey, Class retType) + { + Collection aCollection; + Collection retList; + + retList = new ArrayList(); + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return retList; + + for (Object aObj : aCollection) + { + if (retType.isInstance(aObj) == true) + retList.add(retType.cast(aObj)); + } + + return retList; + } + + /** + * Returns the appropriate singleton w.r.t aKey + */ + public synchronized Object get(Object aKey) + { + Object aSingleton; + + // Insanity check + if (aKey == null) + return null; + + aSingleton = mySingletonMap.get(aKey); + return aSingleton; + } + + @SuppressWarnings("unchecked") + public synchronized G1 get(Object aKey, G1 defaultVal) + { + Object aSingleton; + Class aType; + + // Insanity check + if (aKey == null) + return null; + + if (defaultVal == null) + throw new IllegalArgumentException(); + + // If no value associated with the key then return defaultVal + aSingleton = mySingletonMap.get(aKey); + if (aSingleton == null) + return defaultVal; + + // Ensure the value is of the same type as defaultVal + aType = defaultVal.getClass(); + if (aType.isAssignableFrom(aSingleton.getClass()) == true) + return (G1)(aType.cast(aSingleton)); + + // !if (G1.class.isAssignableFrom(aSingleton.getClass()) == true) + + return defaultVal; + } + + public synchronized G1 get(Object aKey, Class retType, G1 defaultVal) + { + Object aSingleton; + + // Insanity check + if (aKey == null) + return null; + + // If no value associated with the key then return defaultVal + aSingleton = mySingletonMap.get(aKey); + if (aSingleton == null) + return defaultVal; + + if (retType.isInstance(aSingleton) == true) + return retType.cast(aSingleton); + + return defaultVal; + } + + /** + * Returns all of the keys for the singleton map + */ + public synchronized Set keySetForSingletons() + { + return new LinkedHashSet(mySingletonMap.keySet()); + } + + /** + * Returns all of the keys for the multiple map + */ + public synchronized Set keySetForMultiples() + { + return new LinkedHashSet(mySetMap.keySet()); + } + + /** + * Sets aObject as the appropriate singleton w.r.t aKey + */ + public synchronized void put(Object aKey, Object aObject) + { + // Remove the entry from the hashtable if null + if (aObject == null) + mySingletonMap.remove(aKey); + + // Set in the entry for the corresponding aKey + else + mySingletonMap.put(aKey, aObject); + } + + /** + * Removes all the objects from the appropriate collection + */ + public synchronized void removeAllItems(Object aKey) + { + Collection aCollection; + + // Remove the item + // Get the associated collection + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return; + + // Remove the item from the collection + aCollection.clear(); + } + + /** + * Removes aObject from the appropriate Collection w.r.t aKey + */ + public synchronized void removeItem(Object aKey, Object aObject) + { + Collection aCollection; + + // Get the associated collection + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return; + + // Remove the item from the collection + aCollection.remove(aObject); + } + +} diff --git a/src/glum/registry/Registry.java b/src/glum/registry/Registry.java new file mode 100644 index 0000000..27217c4 --- /dev/null +++ b/src/glum/registry/Registry.java @@ -0,0 +1,379 @@ +package glum.registry; + +import java.util.*; + +/** + * The Registry class allows for various types of entities to be linked together by a common key. Outside operators can: + *
      + *
    • 1. Add items associated with the key + *
    • 2. Retrieve a collection of all items associated with the key + *
    • 3. Register to receive notification of any changes associated with a key + *
    • 4. Send notification with regard to a key + *
    + */ +public class Registry +{ + // State var + private Map> mySetMap; + private Map> mySetListeners; + private Map mySingletonMap; + private Map> mySingletonListeners; + + public Registry() + { + mySetMap = new LinkedHashMap>(); + mySetListeners = new LinkedHashMap>(); + + mySingletonMap = new LinkedHashMap(); + mySingletonListeners = new LinkedHashMap>(); + } + + /** + * Manual Destructor + */ + public synchronized void dispose() + { + if (mySetMap != null) + { + mySetMap.clear(); + mySetMap = null; + } + if (mySetListeners != null) + { + mySetListeners.clear(); + mySetListeners = null; + } + + if (mySingletonMap != null) + { + mySingletonMap.clear(); + mySingletonMap = null; + } + if (mySingletonListeners != null) + { + mySingletonListeners.clear(); + mySingletonListeners = null; + } + } + + /** + * Adds in the collection of objects w.r.t aKey + */ + public void addAllResourceItems(Object aKey, Collection aList) + { + Collection aCollection; + + synchronized(this) + { + aCollection = mySetMap.get(aKey); + if (aCollection == null) + { + aCollection = new LinkedHashSet(); + mySetMap.put(aKey, aCollection); + } + aCollection.addAll(aList); + } + + // Notify the listeners + notifyResourceListeners(aKey); + } + + /** + * Adds aObject to the appropriate Collection w.r.t aKey + */ + public void addResourceItem(Object aKey, Object aObject) + { + Collection aCollection; + + synchronized(this) + { + aCollection = mySetMap.get(aKey); + if (aCollection == null) + { + aCollection = new LinkedHashSet(); + mySetMap.put(aKey, aCollection); + } + aCollection.add(aObject); + } + + // Notify the listeners + notifyResourceListeners(aKey); + } + + /** + * Adds Listener associated w.r.t aKey + */ + public void addResourceListener(Object aKey, ResourceListener aListener) + { + Collection aSet; + + synchronized(this) + { + aSet = mySetListeners.get(aKey); + if (aSet == null) + { + aSet = new ArrayList(); + mySetListeners.put(aKey, aSet); + } + aSet.add(aListener); + } + } + + /** + * Adds Listener associated w.r.t aKey + */ + public void addSingletonListener(Object aKey, ResourceListener aListener) + { + Collection aSet; + + synchronized(this) + { + aSet = mySingletonListeners.get(aKey); + if (aSet == null) + { + aSet = new ArrayList(); + mySingletonListeners.put(aKey, aSet); + } + aSet.add(aListener); + } + } + + /** + * Returns the appropriate collection w.r.t aKey + */ + public synchronized List getResourceItems(Object aKey) + { + Collection aCollection; + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return new ArrayList(); + + return new ArrayList(aCollection); + } + + public synchronized List getResourceItems(Object aKey, Class retType) + { + Collection aCollection; + List retList; + + retList = new ArrayList(); + + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return retList; + + for (Object aObj : aCollection) + { + if (retType.isInstance(aObj) == true) + retList.add(retType.cast(aObj)); + } + + return retList; + } + + /** + * Returns the appropriate singleton w.r.t aKey + */ + public synchronized Object getSingleton(Object aKey) + { + Object aSingleton; + + // Insanity check + if (aKey == null) + return null; + + aSingleton = mySingletonMap.get(aKey); + return aSingleton; + } + + public synchronized G1 getSingleton(Object aKey, Class retType) + { + Object aSingleton; + + // Insanity check + if (aKey == null) + return null; + + aSingleton = mySingletonMap.get(aKey); + if (retType.isInstance(aSingleton) == true) + return retType.cast(aSingleton); + + return null; + } + + /** + * Removes all the objects from the appropriate collection + */ + public void removeAllResourceItems(Object aKey) + { + Collection aCollection; + + // Remove the item + synchronized(this) + { + // Get the associated collection + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return; + + // Remove all the items from the collection + aCollection.clear(); + } + + // Notify the listeners + notifyResourceListeners(aKey); + } + + /** + * Removes aObject from the appropriate Collection w.r.t aKey + */ + public void removeResourceItem(Object aKey, Object aObject) + { + Collection aCollection; + + // Remove the item + synchronized(this) + { + // Get the associated collection + aCollection = mySetMap.get(aKey); + if (aCollection == null) + return; + + // Remove the item from the collection + aCollection.remove(aObject); + } + + // Notify the listeners + notifyResourceListeners(aKey); + } + + /** + * Replaces the current collection with aList w.r.t aKey + */ + public void replaceResourceItems(Object aKey, Collection aList) + { + Collection aCollection; + + synchronized(this) + { + aCollection = mySetMap.get(aKey); + if (aCollection == null) + { + aCollection = new LinkedHashSet(); + mySetMap.put(aKey, aCollection); + } + aCollection.clear(); + aCollection.addAll(aList); + } + + // Notify the listeners + notifyResourceListeners(aKey); + } + + /** + * Removes aListener associated w.r.t aKey + */ + public void removeResourceListener(Object aKey, ResourceListener aListener) + { + Collection aSet; + + // Remove the listenr + synchronized(this) + { + if (mySetListeners == null) + return; + + aSet = mySetListeners.get(aKey); + if (aSet == null) + return; + + aSet.remove(aListener); + } + } + + /** + * Removes aListener associated w.r.t aKey + */ + public void removeSingletonListener(Object aKey, ResourceListener aListener) + { + Collection aSet; + + // Remove the listenr + synchronized(this) + { + if (mySingletonListeners == null) + return; + + aSet = mySingletonListeners.get(aKey); + if (aSet == null) + return; + + aSet.remove(aListener); + } + } + + /** + * Sets aObject as the appropriate singleton w.r.t aKey + */ + public void setSingleton(Object aKey, Object aObject) + { + synchronized(this) + { + // Remove the entry from the hashtable if null + if (aObject == null) + mySingletonMap.remove(aKey); + // Set in the entry for the corresponding aKey + else + mySingletonMap.put(aKey, aObject); + } + + // Notify the listeners + notifySingletonListeners(aKey); + } + + /** + * Notifies the listeners that some action has occurred w.r.t aKey + */ + public void notifyResourceListeners(Object aKey) + { + Collection aSet, notifySet; + + // Get the listeners + synchronized(this) + { + aSet = mySetListeners.get(aKey); + if (aSet == null) + return; + + notifySet = new ArrayList(aSet); + } + + // Send out the notifications + for (ResourceListener aListener : notifySet) + aListener.resourceChanged(this, aKey); + } + + /** + * Notifies the listeners that some action has occurred w.r.t aKey + */ + public void notifySingletonListeners(Object aKey) + { + Collection aSet, notifySet; + + // Get the listeners + synchronized(this) + { + aSet = mySingletonListeners.get(aKey); + if (aSet == null) + return; + + notifySet = new ArrayList(aSet); + } + + // Send out the notifications + for (ResourceListener aListener : notifySet) + aListener.resourceChanged(this, aKey); + } + +} diff --git a/src/glum/registry/ResourceListener.java b/src/glum/registry/ResourceListener.java new file mode 100644 index 0000000..ac28bdc --- /dev/null +++ b/src/glum/registry/ResourceListener.java @@ -0,0 +1,9 @@ +package glum.registry; + +public interface ResourceListener +{ + /** + * Event handler + */ + public void resourceChanged(Registry aRegistry, Object aKey); +} diff --git a/src/glum/registry/SelectionListener.java b/src/glum/registry/SelectionListener.java new file mode 100644 index 0000000..9c8381d --- /dev/null +++ b/src/glum/registry/SelectionListener.java @@ -0,0 +1,10 @@ +package glum.registry; + +public interface SelectionListener +{ + /** + * Notification that the selection was changed for the specified class + */ + public void selectionChanged(SelectionManager aManager, Class aClass); + +} diff --git a/src/glum/registry/SelectionManager.java b/src/glum/registry/SelectionManager.java new file mode 100644 index 0000000..de6bf56 --- /dev/null +++ b/src/glum/registry/SelectionManager.java @@ -0,0 +1,201 @@ +package glum.registry; + +import glum.reflect.FunctionRunnable; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.SwingUtilities; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +/** + * Class that support arbitrary item selection. All valid types of selected items must be registered in the Constructor. + */ +public class SelectionManager +{ + // State vars + private Multimap, Object> selectionMap; + private Multimap, SelectionListener> listenerMap; + private Set> registerSet; + private boolean notifyViaAwtThread; + + public SelectionManager(Class... classArr) + { + selectionMap = ArrayListMultimap.create(); + listenerMap = HashMultimap.create(); + + registerSet = Sets.newHashSet(); + for (Class aClass : classArr) + registerSet.add(aClass); + + notifyViaAwtThread = false; + } + + /** + * Adds in the specified Listener as a Listener of the specified item selection changes. Throws RuntimeException if + * aClass was not previously registered in the constructor. + */ + public synchronized void addListener(SelectionListener aListener, Class aClass) + { + if (registerSet.contains(aClass) == true) + listenerMap.put(aClass, aListener); + else + throw new RuntimeException("Unregistered selection class: " + aClass); + } + + /** + * Add to the list of selected items associated with aClass + */ + public void addItem(Class aClass, G1 aItem) + { + addItem(aClass, aItem, null); + } + + /** + * Adds to the list of selected items and notifies all listeners but the specified skipListener + */ + public void addItem(Class aClass, G1 aItem, SelectionListener skipListener) + { + if (registerSet.contains(aClass) == false) + throw new RuntimeException("Unregistered selection class: " + aClass); + + // Replace the old selections with the new item list + synchronized(this) + { + selectionMap.put(aClass, aItem); + } + + notifyListeners(aClass, skipListener); + } + + /** + * Add to the list of selected items associated with aClass + */ + public void addItems(Class aClass, List itemList) + { + addItems(aClass, itemList, null); + } + + /** + * Adds to the list of selected items and notifies all listeners but the specified skipListener + */ + public void addItems(Class aClass, List itemList, SelectionListener skipListener) + { + if (registerSet.contains(aClass) == false) + throw new RuntimeException("Unregistered selection class: " + aClass); + + // Replace the old selections with the new item list + synchronized(this) + { + selectionMap.putAll(aClass, itemList); + } + + notifyListeners(aClass, skipListener); + } + + /** + * Returns the list of selected items associated with aClass + */ + @SuppressWarnings("unchecked") + public synchronized List getSelectedItems(Class aClass) + { + if (registerSet.contains(aClass) == false) + throw new RuntimeException("Unregistered selection class: " + aClass); + + return (List)Lists.newArrayList(selectionMap.get(aClass)); + } + + /** + * Removes from the list of selected items associated with aClass + */ + public void removeItems(Class aClass, List itemList) + { + removeItems(aClass, itemList, null); + } + + /** + * Removes from the list of selected items and notifies all listeners but the specified skipListener + */ + public void removeItems(Class aClass, List itemList, SelectionListener skipListener) + { + if (registerSet.contains(aClass) == false) + throw new RuntimeException("Unregistered selection class: " + aClass); + + // Replace the old selections with the new item list + synchronized(this) + { + Set replaceSet; + + replaceSet = new LinkedHashSet(getSelectedItems(aClass)); + replaceSet.removeAll(itemList); + selectionMap.replaceValues(aClass, replaceSet); + } + + notifyListeners(aClass, skipListener); + } + + /** + * Sets in the selected items and notifies all listeners + */ + public void setItems(Class aClass, List itemList) + { + setItems(aClass, itemList, null); + } + + /** + * Sets in the selected items and notifies all listeners but the specified skipListener + */ + public void setItems(Class aClass, List itemList, SelectionListener skipListener) + { + if (registerSet.contains(aClass) == false) + throw new RuntimeException("Unregistered selection class: " + aClass); + + // Replace the old selections with the new item list + synchronized(this) + { + selectionMap.replaceValues(aClass, itemList); + } + + notifyListeners(aClass, skipListener); + } + + /** + * Method to force all notifications to be sent via the AWT thread. Note if there is some time sensitive code, + * setting this flag may cause it to run slightly slower. + */ + public void setNotificationViaAwtThread(boolean aBool) + { + notifyViaAwtThread = aBool; + } + + /** + * Helper method to notify the listeners associated with the specified class. + */ + private void notifyListeners(Class aClass, SelectionListener skipListener) + { + List listenerList; + + // Ensure this logic is always executed on the AWT thread (if notifyViaAwtThread == true) + if (notifyViaAwtThread == true && SwingUtilities.isEventDispatchThread() == false) + { + SwingUtilities.invokeLater(new FunctionRunnable(this, "notifyListeners", aClass, skipListener)); + return; + } + + synchronized(this) + { + listenerList = Lists.newArrayList(listenerMap.get(aClass)); + listenerList.remove(skipListener); + } + + for (SelectionListener aListener : listenerList) + aListener.selectionChanged(this, aClass); + } + +} diff --git a/src/glum/task/ConsoleTask.java b/src/glum/task/ConsoleTask.java new file mode 100644 index 0000000..dc87bdd --- /dev/null +++ b/src/glum/task/ConsoleTask.java @@ -0,0 +1,188 @@ +package glum.task; + +import com.google.common.base.Strings; + +import glum.unit.NumberUnit; +import glum.unit.Unit; + +public class ConsoleTask implements Task +{ + private boolean isActive; + private double progress; + + private String dynamicMsgFrag; + private String dynamicMsgLast; + private long dynamicMsgRateMs; + private long oldTimeMs; + + private Unit progressUnit; + private boolean showProgressInUpdate; + + private String tabStr; + + public ConsoleTask(boolean aShowProgressInUpdate) + { + isActive = true; + progress = 0; + + dynamicMsgFrag = null; + dynamicMsgLast = null; + dynamicMsgRateMs = 37; + oldTimeMs = Long.MIN_VALUE; + + progressUnit = new NumberUnit("", "", 100, 2); + showProgressInUpdate = aShowProgressInUpdate; + + tabStr = null; + } + + public ConsoleTask() + { + this(false); + } + + @Override + public void abort() + { + isActive = false; + } + + @Override + public void infoAppend(String aMsg) + { + // Force the last dynamic message to be shown + if (dynamicMsgLast != null) + dynamicMsgUpdateForce(dynamicMsgLast, 0); + dynamicMsgLast = null; + + // Update the tab chars + if (tabStr != null) + aMsg = aMsg.replace("\t", tabStr); + + // Display the new message + System.out.print(aMsg); + System.out.flush(); + + // Reset the dynamic vars + dynamicMsgFrag = null; + dynamicMsgLast = null; + oldTimeMs = Long.MIN_VALUE; + } + + @Override + public void infoAppendln(String aMsg) + { + infoAppend(aMsg + '\n'); + } + + @Override + public void infoUpdate(String aMsg) + { + long currTimeMs, totalTimeMs; + + // Auto add the progress into update messages if necessary + dynamicMsgLast = aMsg; + if (showProgressInUpdate == true) + dynamicMsgLast = "[" + progressUnit.getString(getProgress()) +"%] " + aMsg; + + // Get the current time + currTimeMs = System.nanoTime() / 1000000; + + // Has enough time elapsed/ + totalTimeMs = currTimeMs - oldTimeMs; + if (totalTimeMs < dynamicMsgRateMs && totalTimeMs > 0) + return; + + // Worker method + dynamicMsgUpdateForce(dynamicMsgLast, currTimeMs); + dynamicMsgLast = null; + } + + @Override + public double getProgress() + { + return progress; + } + + @Override + public void reset() + { + isActive = true; + progress = 0; + } + + @Override + public void setProgress(double aProgress) + { + progress = aProgress; + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0) / maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + dynamicMsgRateMs = aRateMs; + } + + @Override + public void setStatus(String aStatus) + { + // ConsoleTasks do not support a status line. + ; // Nothing to do + } + + @Override + public void setTabSize(int numSpaces) + { + tabStr = Strings.repeat(" ", numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + // ConsoleTasks do not support a title line. + ; // Nothing to do + } + + @Override + public boolean isActive() + { + return isActive; + } + + /** + * Helper method that does the actual updating of the previous dynamic text with aMsg. + */ + protected void dynamicMsgUpdateForce(String aMsg, long currTimeMs) + { + int numTempChars; + + // Erase the old message + if (dynamicMsgFrag != null) + { + numTempChars = dynamicMsgFrag.length(); + for (int c1 = 0; c1 < numTempChars; c1++) + System.out.print("\b \b"); + } + + // Update the tab chars + if (tabStr != null) + aMsg = aMsg.replace("\t", tabStr); + + // Output the new message + System.out.print(aMsg); + System.out.flush(); + + // Save off the new dynamic message fragment + dynamicMsgFrag = aMsg; + + // Update our internal time + oldTimeMs = currTimeMs; + } + +} diff --git a/src/glum/task/CountTask.java b/src/glum/task/CountTask.java new file mode 100644 index 0000000..28f3f8b --- /dev/null +++ b/src/glum/task/CountTask.java @@ -0,0 +1,110 @@ +package glum.task; + +/** + * Task that automatically updates the progress based on a count. + * Once the currCount == fullCount then the Task is Complete. + */ +public class CountTask implements Task +{ + protected Task refTask; + protected int fullCount; + protected int currCount; + + public CountTask(Task aRefTask, int aFullCount) + { + refTask = aRefTask; + fullCount = aFullCount; + currCount = 0; + } + + /** + * Increment the currCount (and corresponding) progress to + * completion. + */ + public void incrementCount() + { + currCount++; + refTask.setProgress(currCount, fullCount); + } + + @Override + public void abort() + { + refTask.abort(); + } + + @Override + public void infoAppend(String aMsg) + { + refTask.infoAppend(aMsg); + } + + @Override + public void infoAppendln(String aMsg) + { + refTask.infoAppendln(aMsg); + } + + @Override + public void infoUpdate(String aMsg) + { + refTask.infoUpdate(aMsg); + } + + @Override + public double getProgress() + { + return (currCount + 0.0) / fullCount; + } + + @Override + public void reset() + { + currCount = 0; + refTask.setProgress(currCount, fullCount); + } + + @Override + public void setProgress(double aProgress) + { + // The only way to increase the progress is to increment the count. + ; // Nothing to do + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0)/ maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + refTask.setRefreshRateMs(aRateMs); + } + + @Override + public void setStatus(String aStatus) + { + refTask.setStatus(aStatus); + } + + @Override + public void setTabSize(int numSpaces) + { + refTask.setTabSize(numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + refTask.setTitle(aTitle); + } + + @Override + public boolean isActive() + { + return refTask.isActive(); + } + +} diff --git a/src/glum/task/FileLogTask.java b/src/glum/task/FileLogTask.java new file mode 100644 index 0000000..b1188eb --- /dev/null +++ b/src/glum/task/FileLogTask.java @@ -0,0 +1,166 @@ +package glum.task; + +import glum.unit.NumberUnit; +import glum.unit.Unit; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; + +import com.google.common.base.Strings; + +public class FileLogTask implements Task +{ + private boolean isActive; + private double progress; + private String tabStr; + + private String dynamicMsgLast; + private Unit progressUnit; + private boolean showProgressInUpdate; + + private PrintStream printStream; + + public FileLogTask(File aFile, boolean isAppend, boolean aShowProgressInUpdate) + { + isActive = true; + progress = 0; + tabStr = null; + + dynamicMsgLast = null; + progressUnit = new NumberUnit("", "", 100, 2); + showProgressInUpdate = aShowProgressInUpdate; + + // Open the file stream for writing + try + { + printStream = new PrintStream(new FileOutputStream(aFile, isAppend)); + } + catch (FileNotFoundException aExp) + { + throw new RuntimeException(aExp); + } + + } + + public FileLogTask(File aFile, boolean isAppend) + { + this(aFile, isAppend, false); + } + + /** + * Properly close the associated file used for the log + */ + public void close() + { + printStream.close(); + } + + /** + * Configures whether the dynamic messages should have an automatic progress bar readout. + */ + public void setShowProgressInUpdate(boolean aShowProgressInUpdate) + { + showProgressInUpdate = aShowProgressInUpdate; + } + + @Override + public void abort() + { + isActive = false; + } + + @Override + public void infoAppend(String aMsg) + { + // Force the last dynamic message to output + if (dynamicMsgLast != null) + printStream.print(dynamicMsgLast); + dynamicMsgLast = null; + + // Update the tab chars + if (tabStr != null) + aMsg = aMsg.replace("\t", tabStr); + + // Display the new message + printStream.print(aMsg); + printStream.flush(); + } + + @Override + public void infoAppendln(String aMsg) + { + infoAppend(aMsg + '\n'); + } + + @Override + public void infoUpdate(String aMsg) + { + // Update the dynamic message + dynamicMsgLast = aMsg; + + // Auto add the progress into update messages if necessary + if (showProgressInUpdate == true) + dynamicMsgLast = "[" + progressUnit.getString(getProgress()) + "%] " + aMsg; + } + + @Override + public double getProgress() + { + return progress; + } + + @Override + public void reset() + { + isActive = true; + progress = 0; + } + + @Override + public void setProgress(double aProgress) + { + progress = aProgress; + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0) / maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + // Refresh rate is nonsensical for a file log + ; // Nothing to do + } + + @Override + public void setStatus(String aStatus) + { + // FileLogTasks do not support a status line. + ; // Nothing to do + } + + @Override + public void setTabSize(int numSpaces) + { + tabStr = Strings.repeat(" ", numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + // FileLogTasks do not support a title line. + ; // Nothing to do + } + + @Override + public boolean isActive() + { + return isActive; + } + +} diff --git a/src/glum/task/PartialTask.java b/src/glum/task/PartialTask.java new file mode 100644 index 0000000..12557a9 --- /dev/null +++ b/src/glum/task/PartialTask.java @@ -0,0 +1,99 @@ +package glum.task; + +public class PartialTask implements Task +{ + protected Task refTask; + protected double progressOffset; + protected double progressTotalFragment; + protected double internalProgress; + + public PartialTask(Task aRefTask, double aProgressOffset, double aProgressTotalFragment) + { + refTask = aRefTask; + progressOffset = aProgressOffset; + progressTotalFragment = aProgressTotalFragment; + internalProgress = 0; + } + + @Override + public void abort() + { + refTask.abort(); + } + + @Override + public void infoAppend(String aMsg) + { + refTask.infoAppend(aMsg); + } + + @Override + public void infoAppendln(String aMsg) + { + refTask.infoAppendln(aMsg); + } + + @Override + public void infoUpdate(String aMsg) + { + refTask.infoUpdate(aMsg); + } + + @Override + public double getProgress() + { + return progressOffset + (progressTotalFragment * internalProgress); + } + + @Override + public void reset() + { + setProgress(0); + } + + @Override + public void setProgress(double aProgress) + { + internalProgress = aProgress; + + // Update the refTask with the appropriate task value + refTask.setProgress(this.getProgress()); + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0) / maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + refTask.setRefreshRateMs(aRateMs); + } + + @Override + public void setStatus(String aStatus) + { + refTask.setStatus(aStatus); + } + + @Override + public void setTabSize(int numSpaces) + { + refTask.setTabSize(numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + refTask.setTitle(aTitle); + } + + @Override + public boolean isActive() + { + return refTask.isActive(); + } + +} diff --git a/src/glum/task/SilentTask.java b/src/glum/task/SilentTask.java new file mode 100644 index 0000000..e6d5a66 --- /dev/null +++ b/src/glum/task/SilentTask.java @@ -0,0 +1,93 @@ +package glum.task; + +public class SilentTask implements Task +{ + private boolean isActive; + private double progress; + + public SilentTask() + { + isActive = true; + progress = 0; + } + + @Override + public void abort() + { + isActive = false; + } + + @Override + public void infoAppend(String aMsg) + { + ; // Nothing to do + } + + @Override + public void infoAppendln(String aMsg) + { + ; // Nothing to do + } + + @Override + public void infoUpdate(String aMsg) + { + ; // Nothing to do + } + + @Override + public double getProgress() + { + return progress; + } + + @Override + public void reset() + { + isActive = true; + progress = 0; + } + + @Override + public void setProgress(double aProgress) + { + progress = aProgress; + } + + @Override + public void setProgress(int currVal, int maxVal) + { + setProgress((currVal + 0.0) / maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + ; // Nothing to do + } + + @Override + public void setStatus(String aStatus) + { + ; // Nothing to do + } + + @Override + public void setTabSize(int numSpaces) + { + ; // Nothing to do + } + + @Override + public void setTitle(String aTitle) + { + ; // Nothing to do + } + + @Override + public boolean isActive() + { + return isActive; + } + +} diff --git a/src/glum/task/SplitTask.java b/src/glum/task/SplitTask.java new file mode 100644 index 0000000..dffb83e --- /dev/null +++ b/src/glum/task/SplitTask.java @@ -0,0 +1,117 @@ +package glum.task; + +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Task designed to encompass many "sub" child tasks and forward actions to each child task. + *

    + * On any query method call such as {@link #getProgress()}, the first child task specified in the constructor will be + * utilized as the task to fulfill the query. + */ +public class SplitTask implements Task +{ + private List childTaskList; + + /** + * @param aTaskArr + * The list of child tasks for which this SplitTask will automatically forward the method calls to. + */ + public SplitTask(Task... aTaskArr) + { + childTaskList = Lists.newLinkedList(); + for (Task aTask : aTaskArr) + childTaskList.add(aTask); + } + + @Override + public void abort() + { + for (Task aTask : childTaskList) + aTask.abort(); + } + + @Override + public void infoAppend(String aMsg) + { + for (Task aTask : childTaskList) + aTask.infoAppend(aMsg); + } + + @Override + public void infoAppendln(String aMsg) + { + for (Task aTask : childTaskList) + aTask.infoAppendln(aMsg); + } + + @Override + public void infoUpdate(String aMsg) + { + for (Task aTask : childTaskList) + aTask.infoUpdate(aMsg); + } + + @Override + public double getProgress() + { + return childTaskList.get(0).getProgress(); + } + + @Override + public void reset() + { + for (Task aTask : childTaskList) + aTask.reset(); + } + + @Override + public void setProgress(double aProgress) + { + for (Task aTask : childTaskList) + aTask.setProgress(aProgress); + } + + @Override + public void setProgress(int currVal, int maxVal) + { + for (Task aTask : childTaskList) + aTask.setProgress(currVal, maxVal); + } + + @Override + public void setRefreshRateMs(long aRateMs) + { + for (Task aTask : childTaskList) + aTask.setRefreshRateMs(aRateMs); + } + + @Override + public void setStatus(String aStatus) + { + for (Task aTask : childTaskList) + aTask.setStatus(aStatus); + } + + @Override + public void setTabSize(int numSpaces) + { + for (Task aTask : childTaskList) + aTask.setTabSize(numSpaces); + } + + @Override + public void setTitle(String aTitle) + { + for (Task aTask : childTaskList) + aTask.setTitle(aTitle); + } + + @Override + public boolean isActive() + { + return childTaskList.get(0).isActive(); + } + +} diff --git a/src/glum/task/Task.java b/src/glum/task/Task.java new file mode 100644 index 0000000..9b5fca4 --- /dev/null +++ b/src/glum/task/Task.java @@ -0,0 +1,78 @@ +package glum.task; + +public interface Task +{ + /** + * Method to allow the Task to be properly aborted. After this method + * call, the method call isActive will return false. + */ + public void abort(); + + /** + * Appends aMsg to the info buffer. The text aMsg can not be updated. + */ + public void infoAppend(String aMsg); + + /** + * Appends aMsg to the info buffer. The text aMsg can not be updated. + * A new line will automatically be added to the buffer after aMsg. + */ + public void infoAppendln(String aMsg); + + /** + * Updates the previous update message with the text aMsg. The text + * aMsg can be updated as long as the method infoAppend is not called. + */ + public void infoUpdate(String aMsg); + + /** + * Returns the percent of progress that has been completed. + * 0.0: Not started + * 1.0: Complete + */ + public double getProgress(); + + /** + * Method to reset a Task to its initial state. + */ + public void reset(); + + /** + * Sets in the percent of progress that has been completed. + * 0.0: Not started + * 1.0: Complete + */ + public void setProgress(double aProgress); + + /** + * Sets it the progress of a task so that its completion + * value is a ratio of currVal to maxVal. + */ + public void setProgress(int currVal, int maxVal); + + /** + * Sets in the maximum rate the UI will be refreshed at. + */ + public void setRefreshRateMs(long aRateMs); + + /** + * Method that sets the single line status of the task + */ + public void setStatus(String aStatus); + + /** + * Sets in the number of spaces the tab character will be converted to + */ + public void setTabSize(int numSpaces); + + /** + * Method that sets the title of the task + */ + public void setTitle(String aTitle); + + /** + * Returns whether this task is still active + */ + public boolean isActive(); + +} diff --git a/src/glum/task/TaskState.java b/src/glum/task/TaskState.java new file mode 100644 index 0000000..39c694f --- /dev/null +++ b/src/glum/task/TaskState.java @@ -0,0 +1,20 @@ +package glum.task; + +public enum TaskState +{ + // Initial state + Inactive, + + // Default normal state + Active, + + // Transitional states + TransAbort, + TransComplete, + TransSuspend, + + // Terminal states + Aborted, + Completed, + Suspended +} diff --git a/src/glum/unit/BaseUnitProvider.java b/src/glum/unit/BaseUnitProvider.java new file mode 100644 index 0000000..67a4ddf --- /dev/null +++ b/src/glum/unit/BaseUnitProvider.java @@ -0,0 +1,48 @@ +package glum.unit; + +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Base UnitProvider class that provides the functionality of listener registration and notification. + */ +public abstract class BaseUnitProvider implements UnitProvider +{ + private List myListeners; + private String refName; + + public BaseUnitProvider(String aRefName) + { + myListeners = Lists.newLinkedList(); + refName = aRefName; + } + + @Override + public String getDisplayName() + { + return refName; + } + + @Override + public void addListener(UnitListener aListener) + { + myListeners.add(aListener); + } + + @Override + public void removeListener(UnitListener aListener) + { + myListeners.remove(aListener); + } + + /** + * Utility method to notify our listeners + */ + protected void notifyListeners() + { + for (UnitListener aListener : myListeners) + aListener.unitChanged(this, refName); + } + +} diff --git a/src/glum/unit/ByteUnit.java b/src/glum/unit/ByteUnit.java new file mode 100644 index 0000000..cf14be4 --- /dev/null +++ b/src/glum/unit/ByteUnit.java @@ -0,0 +1,67 @@ +package glum.unit; + +public class ByteUnit extends HeuristicUnit +{ + // Constants + public static final double Kilobyte = 1024; + public static final double Megabyte = 1024 * 1024; + public static final double Gigabyte = 1024 * 1024 * 1024; + public static final double Terabyte = 1024 * 1024 * 1024L * 1024L; + + public ByteUnit(int numDecimalPlaces) + { + super("Heuristic", numDecimalPlaces); + } + + @Override + public String getString(Object aVal) + { + String aStr, unitStr; + long numBytes; + double dVal; + + aStr = "N/A"; + if (aVal instanceof Number == false) + return aStr; + + numBytes = ((Number)aVal).longValue(); + + if (numBytes < Kilobyte) + { + unitStr = "B"; + dVal = numBytes; + } + else if (numBytes < Megabyte) + { + unitStr = "KB"; + dVal = (numBytes + 0.0) / Kilobyte; + } + else if (numBytes < Gigabyte) + { + unitStr = "MB"; + dVal = (numBytes + 0.0) / Megabyte; + } + else if (numBytes < Terabyte) + { + unitStr = "GB"; + dVal = (numBytes + 0.0) / Gigabyte; + } + else + { + unitStr = "TB"; + dVal = (numBytes + 0.0) / Terabyte; + } + + synchronized (format) + { + return format.format(dVal) + " " + unitStr; + } + } + + @Override + public HeuristicUnit spawnClone(int numDecimalPlaces) + { + return new ByteUnit(numDecimalPlaces); + } + +} diff --git a/src/glum/unit/ConstUnitProvider.java b/src/glum/unit/ConstUnitProvider.java new file mode 100644 index 0000000..6cfb78d --- /dev/null +++ b/src/glum/unit/ConstUnitProvider.java @@ -0,0 +1,63 @@ +package glum.unit; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; + +/** + * UnitProvider that always returns the same Unit. + */ +public class ConstUnitProvider implements UnitProvider +{ + // State vars + private Unit activeUnit; + + public ConstUnitProvider(Unit aUnit) + { + activeUnit = aUnit; + } + + @Override + public void addListener(UnitListener aListener) + { + ; // Nothing to do + } + + @Override + public void removeListener(UnitListener aListener) + { + ; // Nothing to do + } + + @Override + public String getConfigName() + { + return "Const"; + } + + @Override + public String getDisplayName() + { + return "Const"; + } + + @Override + public Unit getUnit() + { + return activeUnit; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + ; // Nothing to do + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + ; // Nothing to do + } + +} diff --git a/src/glum/unit/Convert.java b/src/glum/unit/Convert.java new file mode 100644 index 0000000..349fc1c --- /dev/null +++ b/src/glum/unit/Convert.java @@ -0,0 +1,90 @@ +package glum.unit; + +/** Contains conversion multipliers to/from feet, yards, meters, data miles, +* and nautical miles, as well as angular values to/from degrees and radians. +* To convert a value X in units of U to units of +* V, use X * Convert.U_TO_V. +*/ +public class Convert +{ + public static final double FEET_TO_METERS = 0.3048; + public static final double DM_TO_METERS = 1828.8; + public static final double NM_TO_METERS = 1852.0; + public static final double MILES_TO_METERS = 1609.344; + public static final double YARDS_TO_METERS = 0.9144; // 3 * FEET_TO_METERS + + public static final double METERS_TO_FEET = 1.0 / FEET_TO_METERS; + public static final double DM_TO_FEET = 6000.0; + public static final double NM_TO_FEET = NM_TO_METERS * METERS_TO_FEET; + public static final double MILES_TO_FEET = 5280.0; + public static final double YARDS_TO_FEET = 3.0; + + public static final double METERS_TO_DM = 1.0 / DM_TO_METERS; + public static final double FEET_TO_DM = FEET_TO_METERS * METERS_TO_DM; + public static final double NM_TO_DM = NM_TO_METERS * METERS_TO_DM; + public static final double MILES_TO_DM = MILES_TO_METERS * METERS_TO_DM; + public static final double YARDS_TO_DM = YARDS_TO_METERS * METERS_TO_DM; + + public static final double METERS_TO_NM = 1.0 / NM_TO_METERS; + public static final double FEET_TO_NM = FEET_TO_METERS * METERS_TO_NM; + public static final double DM_TO_NM = DM_TO_METERS * METERS_TO_NM; + public static final double MILES_TO_NM = MILES_TO_METERS * METERS_TO_NM; + public static final double YARDS_TO_NM = YARDS_TO_METERS * NM_TO_METERS; + + public static final double METERS_TO_MILES = 1.0 / MILES_TO_METERS; + public static final double FEET_TO_MILES = FEET_TO_METERS * METERS_TO_MILES; + public static final double DM_TO_MILES = DM_TO_METERS * METERS_TO_MILES; + public static final double NM_TO_MILES = NM_TO_METERS * METERS_TO_MILES; + public static final double YARDS_TO_MILES = YARDS_TO_METERS * METERS_TO_MILES; + + public static final double METERS_TO_YARDS = 1.0 / YARDS_TO_METERS; + public static final double FEET_TO_YARDS = 1.0 / 3.0; + public static final double DM_TO_YARDS = 2000.0; + public static final double NM_TO_YARDS = NM_TO_METERS * METERS_TO_YARDS; + public static final double MILES_TO_YARDS = 1760.0; + + public static final double RAD_TO_DEG = 180.0 / Math.PI; + public static final double DEG_TO_RAD = Math.PI / 180.0; + + public static final double SECS_TO_MSECS = 1000.0; + public static final double MSECS_TO_SECS = 1.0 / SECS_TO_MSECS; + + public static final int MINS_TO_SECS = 60; + public static final int HOURS_TO_MINS = 60; + public static final int HOURS_TO_SECS = HOURS_TO_MINS * MINS_TO_SECS; + + public static final double SECS_TO_MINS = 1 / MINS_TO_SECS; + public static final double MINS_TO_HOURS = 1 / HOURS_TO_MINS; + public static final double SECS_TO_HOURS = 1 / HOURS_TO_SECS; + + + /** + * angleToBearing - Converts an angle to a bearing + */ + public static double angleToBearing(double aAngle) + { + double bearing; + + bearing = 180 - (aAngle + 90); + if (bearing < 0) + bearing += 360; + + return bearing; + } + + + /** + * beairngToAngle - Converts a bearing to an angle + */ + public static double bearingToAngle(double aBearing) + { + double angle; + + angle = 180 - (aBearing + 90); + if (angle < 0) + angle += 360; + + return angle; + } + +} diff --git a/src/glum/unit/DateUnit.java b/src/glum/unit/DateUnit.java new file mode 100644 index 0000000..a46882b --- /dev/null +++ b/src/glum/unit/DateUnit.java @@ -0,0 +1,164 @@ +package glum.unit; + +import java.text.*; +import java.util.*; + +public class DateUnit implements Unit +{ + // State vars + private String configName; + private SimpleDateFormat format; + + private String nullStr; + private boolean markZeroAsNull; + + /** + * Constructor + */ + public DateUnit(String aConfigName, String aFmtStr, TimeZone aTimeZone) + { + configName = aConfigName; + format = new SimpleDateFormat(aFmtStr); + if (aTimeZone != null) + format.setTimeZone(aTimeZone); + + nullStr = "None"; + markZeroAsNull = false; + } + + public DateUnit(String aConfigName, String aFmtStr) + { + this(aConfigName, aFmtStr, null); + } + + public DateUnit(String aConfigName) + { + this(aConfigName, "yyyy-MM-dd", null); + } + + /** + * Returns the TimeZone associated with this unit + */ + public TimeZone getTimeZone() + { + if (format == null) + return TimeZone.getDefault(); + + return format.getTimeZone(); + } + + /** + * Method to cause long values of 0 (UTC == 0) to be displayed in the same fashion as null rather than as the + * formated date of 1969Dec31... + */ + public void setMarkZeroAsNull(boolean aBool) + { + markZeroAsNull = aBool; + } + + /** + * Sets in the string representation for null (or invalid) values + */ + public void setNaNString(String aStr) + { + nullStr = aStr; + } + + /** + * Method to parse the UTC time using this DateUnit's configuration to interpret aStr + */ + public long parseString(String aTimeStr, long eTime) + { + Date aDate; + long aTime; + + try + { + aDate = format.parse(aTimeStr); + aTime = aDate.getTime(); + } + catch (ParseException aExp) + { + aTime = eTime; + aExp.printStackTrace(); + } + + return aTime; + } + + @Override + public String getConfigName() + { + return configName; + } + + @Override + public SimpleDateFormat getFormat() + { + if (format == null) + return null; + + return (SimpleDateFormat)format.clone(); + } + + @Override + public String getLabel(boolean isDetailed) + { + return ""; + } + + @Override + public String getString(Object aVal) + { + Date aDate; + + // Retrieve the appropriate Date object + aDate = null; + if (aVal instanceof Calendar) + aDate = ((Calendar)aVal).getTime(); + else if (aVal instanceof Date) + aDate = (Date)aVal; + else if (aVal instanceof Long) + { + Long aLong; + + aLong = (Long)aVal; + if (aLong == 0 && markZeroAsNull == true) + return nullStr; + + Calendar aCalendar; + aCalendar = new GregorianCalendar(); + aCalendar.setTimeInMillis(aLong); + aDate = aCalendar.getTime(); + } + else + return nullStr; + + return format.format(aDate); + } + + @Override + public String getString(Object aVal, boolean isDetailed) + { + return getString(aVal); + } + + @Override + public double parseString(String aStr, double eVal) + { + throw new UnsupportedOperationException(); + } + + @Override + public double toModel(double aVal) + { + return aVal; + } + + @Override + public double toUnit(double aVal) + { + return aVal; + } + +} diff --git a/src/glum/unit/DateUnitProvider.java b/src/glum/unit/DateUnitProvider.java new file mode 100644 index 0000000..139cb48 --- /dev/null +++ b/src/glum/unit/DateUnitProvider.java @@ -0,0 +1,173 @@ +package glum.unit; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class DateUnitProvider extends BaseUnitProvider +{ + // Config vars + private boolean isCustom; + private TimeZone cfgTimeZone; + private String cfgProtoName; + private String cfgCustomPattern; + + // State vars + private Map protoMap; + private DateUnit activeUnit; + + public DateUnitProvider(String aRefName, DateUnit aActiveUnit) + { + super(aRefName); + + // Config vars + isCustom = false; + cfgTimeZone = aActiveUnit.getFormat().getTimeZone(); + cfgProtoName = null; + cfgCustomPattern = aActiveUnit.getFormat().toPattern(); + + // State vars + protoMap = Maps.newHashMap(); + activeUnit = null; + + // Activate the default (prototype) unit + addProtoUnit(aActiveUnit); + activateProto(cfgTimeZone, cfgProtoName); +// activateCustom(cfgTimeZone, cfgPattern); + } + + /** + * Adds in the specified Unit as a prototype (which can be used to configure the active unit) + */ + public void addProtoUnit(DateUnit aUnit) + { + // First proto unit will be used as the default proto unit + if (cfgProtoName == null) + cfgProtoName = aUnit.getConfigName(); + + protoMap.put(aUnit.getConfigName(), aUnit); + } + + /** + * Updates the activeUnit with the specified custom configuration + */ + public void activateCustom(TimeZone aTimeZone, String aPattern) + { + cfgTimeZone = aTimeZone; + cfgCustomPattern = aPattern; + isCustom = true; + + activeUnit = buildDateUnit("Custom", aPattern, cfgTimeZone); + notifyListeners(); + } + + /** + * Updates the activeUnit with the specified proto configuration + */ + public void activateProto(TimeZone aTimeZone, String aProtoName) + { + DateUnit protoUnit; + + // Ensure this proto unit is installed + protoUnit = protoMap.get(aProtoName); + if (protoUnit == null) + throw new RuntimeException("Specified name is not installed as a prototype! aProtoName: " + aProtoName); + + cfgTimeZone = aTimeZone; + cfgProtoName = aProtoName; + isCustom = false; + + activeUnit = buildDateUnit(cfgProtoName, protoUnit.getFormat().toPattern(), cfgTimeZone); + notifyListeners(); + } + + /** + * Returns whether the current active unit is a custom unit + */ + public boolean getIsCustom() + { + return isCustom; + } + + /** + * Returns the previously specified custom pattern + */ + public String getCustomPattern() + { + return cfgCustomPattern; + } + + /** + * Returns the previously specified proto unit + */ + public DateUnit getProtoUnit() + { + return protoMap.get(cfgProtoName); + } + + /** + * Returns all of the valid proto names + */ + public List getProtoNameList() + { + return Lists.newArrayList(protoMap.keySet()); + } + + @Override + public String getConfigName() + { + return activeUnit.getConfigName(); + } + + @Override + public DateUnit getUnit() + { + return activeUnit; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + // Read the configuration + aStream.readVersion(0); + + isCustom = aStream.readBool(); + cfgTimeZone = TimeZone.getTimeZone(aStream.readString()); + cfgProtoName = aStream.readString(); + cfgCustomPattern = aStream.readString(); + + // Activate the unit + if (isCustom == true) + activateCustom(cfgTimeZone, cfgCustomPattern); + else + activateProto(cfgTimeZone, cfgProtoName); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + // Write the configuration + aStream.writeVersion(0); + + aStream.writeBool(isCustom); + aStream.writeString(cfgTimeZone.getID()); + aStream.writeString(cfgProtoName); + aStream.writeString(cfgCustomPattern); + } + + /** + * Helper method to construct the Unit. Subclass this method if you wish to use an alternative DateUnit. + */ + protected DateUnit buildDateUnit(String aConfigName, String aPattern, TimeZone aTimeZone) + { + return new DateUnit(aConfigName, aPattern, aTimeZone); + } + +} diff --git a/src/glum/unit/DecimalUnitProvider.java b/src/glum/unit/DecimalUnitProvider.java new file mode 100644 index 0000000..37d1ec7 --- /dev/null +++ b/src/glum/unit/DecimalUnitProvider.java @@ -0,0 +1,179 @@ +package glum.unit; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; +import java.text.Format; +import java.text.NumberFormat; +import java.util.List; + +import com.google.common.collect.Lists; + +public class DecimalUnitProvider extends BaseUnitProvider +{ + // State vars + private List protoUnitList; + private Unit activeUnit; + + public DecimalUnitProvider(String aRefName, Unit aActiveUnit) + { + super(aRefName); + + protoUnitList = Lists.newLinkedList(); + activeUnit = aActiveUnit; + } + + /** + * Updates the activeUnit with the specified configuration + */ + public void activate(Unit protoUnit, int decimalPlaces, boolean forceFullLabel) + { + String formatStr; + + // Insanity check + if (protoUnit == null) + return; + + // Build the format + formatStr = "###,###,###,###,###,##0"; + if (decimalPlaces > 0) + { + formatStr += "."; + for (int c1 = 0; c1 < decimalPlaces; c1++) + formatStr += "0"; + } + + if (protoUnit instanceof HeuristicUnit) + activeUnit = ((HeuristicUnit)protoUnit).spawnClone(decimalPlaces); + else if (forceFullLabel == true) + activeUnit = new NumberUnit(protoUnit.getLabel(true), protoUnit.getLabel(true), protoUnit.toUnit(1), formatStr); + else + activeUnit = new NumberUnit(protoUnit.getLabel(true), protoUnit.getLabel(false), protoUnit.toUnit(1), formatStr); + + notifyListeners(); + } + + /** + * Adds in the specified Unit as a prototype (which can be used to configure + * the active unit) + */ + public void addProtoUnit(Unit aProtoUnit) + { + // Insanity check + if (aProtoUnit instanceof HeuristicUnit == false && aProtoUnit instanceof NumberUnit == false) + throw new RuntimeException("ProtoUnit must either be of type HeuristicUnit or NumberUnit"); + + protoUnitList.add(aProtoUnit); + } + + /** + * Returns the number of decimal places allowed in the current unit + */ + public int getDecimalPlaces() + { + Format aFormat; + + aFormat = activeUnit.getFormat(); + if (aFormat instanceof NumberFormat) + return ((NumberFormat)aFormat).getMaximumFractionDigits(); + + return 0; + } + + /** + * Returns whether the unit is forced to show only the detailed label + */ + public boolean getForceFullLabel() + { + String fullLabel, shortLabel; + + if (activeUnit instanceof HeuristicUnit) + return false; + + fullLabel = activeUnit.getLabel(true); + shortLabel = activeUnit.getLabel(false); + return fullLabel.equals(shortLabel); + } + + /** + * Returns the prototype unit corresponding to the current active unit + */ + public Unit getProtoUnit() + { + String activeLabel, protoLabel; + + activeLabel = activeUnit.getLabel(true); + for (Unit aUnit : protoUnitList) + { + protoLabel = aUnit.getLabel(true); + if (protoLabel.equals(activeLabel) == true) + return aUnit; + } + + return null; + } + + /** + * Returns all of the possible prototype units + */ + public List getProtoUnitList() + { + return Lists.newArrayList(protoUnitList); + } + + @Override + public String getConfigName() + { + return activeUnit.getConfigName(); + } + + @Override + public Unit getUnit() + { + return activeUnit; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + Unit protoUnit; + int protoUnitIdx; + int decimalPlaces; + boolean forceFullLabel; + + // Read the stream's content + aStream.readVersion(0); + + protoUnitIdx = aStream.readInt(); + decimalPlaces = aStream.readInt(); + forceFullLabel = aStream.readBool(); + + // Install the configuration + protoUnit = null; + if (protoUnitIdx >=0 && protoUnitIdx < protoUnitList.size()) + protoUnit = protoUnitList.get(protoUnitIdx); + activate(protoUnit, decimalPlaces, forceFullLabel); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + int protoUnitIdx; + int decimalPlaces; + boolean forceFulLabel; + + // Retrieve the configuration + protoUnitIdx = protoUnitList.indexOf(getProtoUnit()); + decimalPlaces = getDecimalPlaces(); + forceFulLabel = getForceFullLabel(); + + // Write the stream's contents + aStream.writeVersion(0); + + aStream.writeInt(protoUnitIdx); + aStream.writeInt(decimalPlaces); + aStream.writeBool(forceFulLabel); + } + +} diff --git a/src/glum/unit/EmptyUnitProvider.java b/src/glum/unit/EmptyUnitProvider.java new file mode 100644 index 0000000..878c723 --- /dev/null +++ b/src/glum/unit/EmptyUnitProvider.java @@ -0,0 +1,52 @@ +package glum.unit; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; + +public class EmptyUnitProvider implements UnitProvider +{ + @Override + public void addListener(UnitListener aListener) + { + ; // Nothing to do + } + + @Override + public void removeListener(UnitListener aListener) + { + ; // Nothing to do + } + + @Override + public String getConfigName() + { + return "None"; + } + + @Override + public String getDisplayName() + { + return "Null Unit"; + } + + @Override + public Unit getUnit() + { + return null; + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + ; // Nothing to do + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + ; // Nothing to do + } + +} diff --git a/src/glum/unit/HeuristicUnit.java b/src/glum/unit/HeuristicUnit.java new file mode 100644 index 0000000..bb2ae28 --- /dev/null +++ b/src/glum/unit/HeuristicUnit.java @@ -0,0 +1,85 @@ +package glum.unit; + +import java.text.DecimalFormat; +import java.text.Format; + +/** + * A special kind of Unit that converts the value to the most human readable format. Thus, this Unit does not support + * any internal model, and calling associated model conversion routines will throw an + * {@link UnsupportedOperationException}.
    + *
    + * The primary method to override is getString(Object aVal) + */ +public abstract class HeuristicUnit implements Unit +{ + // State vars + private String configName; + protected DecimalFormat format; + + public HeuristicUnit(String aConfigName, int numDecimalPlaces) + { + String aStr; + + configName = aConfigName; + + aStr = "#0"; + if (numDecimalPlaces > 0) + { + aStr = "#0."; + for (int c1 = 0; c1 < numDecimalPlaces; c1++) + aStr += "0"; + } + format = new DecimalFormat(aStr); + } + + /** + * Spawns a near exact copy of this HeuristicUnit (only different number of decimal places) + */ + public abstract HeuristicUnit spawnClone(int numDecimalPlaces); + + @Override + public String getConfigName() + { + return configName; + } + + @Override + public Format getFormat() + { + if (format == null) + return null; + + return (Format)format.clone(); + } + + @Override + public String getLabel(boolean isDetailed) + { + return ""; + } + + @Override + public String getString(Object aVal, boolean isDetailed) + { + return getString(aVal); + } + + @Override + public double parseString(String aStr, double eVal) + { + throw new UnsupportedOperationException(); + } + + @Override + public double toModel(double aVal) + { + throw new UnsupportedOperationException(); + } + + @Override + public double toUnit(double aVal) + { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/glum/unit/LatLonUnitProvider.java b/src/glum/unit/LatLonUnitProvider.java new file mode 100644 index 0000000..5a33fac --- /dev/null +++ b/src/glum/unit/LatLonUnitProvider.java @@ -0,0 +1,173 @@ +package glum.unit; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; +import java.text.Format; +import java.text.NumberFormat; + +public class LatLonUnitProvider extends BaseUnitProvider +{ + // State vars + private Unit latUnit, lonUnit; + + public LatLonUnitProvider(String aRefName) + { + super(aRefName); + + activateStandard(false); + } + + /** + * Updates the active lat,lon unit to be configured in raw format + */ + public void activateRaw(int aDecimalPlaces, boolean aIsZeroCentered) + { + // Set in the unit + if (aIsZeroCentered == true) + { + latUnit = new ShiftedUnit("", "", 0, aDecimalPlaces); + lonUnit = new ShiftedUnit("", "", 0, aDecimalPlaces); + } + else + { + latUnit = new ShiftedUnit("", "", 90, aDecimalPlaces); + lonUnit = new ShiftedUnit("", "", 180, aDecimalPlaces); + } + + notifyListeners(); + } + + /** + * Updates the active lat,lon unit to be configured in standard format + */ + public void activateStandard(boolean aIsSecondsShown) + { + latUnit = new LatUnit(aIsSecondsShown); + lonUnit = new LonUnit(aIsSecondsShown); + notifyListeners(); + } + + /** + * Returns the number of decimal places used in the raw unit. + */ + public int getDecimalPlaces() + { + Format aFormat; + + aFormat = latUnit.getFormat(); + if (aFormat instanceof NumberFormat) + return ((NumberFormat)aFormat).getMaximumFractionDigits(); + + return 0; + } + + /** + * Returns the active lat unit + */ + public Unit getLatUnit() + { + return latUnit; + } + + /** + * Returns the active lon unit + */ + public Unit getLonUnit() + { + return lonUnit; + } + + /** + * Returns whether the raw unit is zero centered. This is true only for properly configured active raw units. + */ + public boolean isZeroCentered() + { + if (latUnit instanceof LatUnit) + return false; + + return (latUnit.toModel(0) == 0); + } + + /** + * Returns whether the active lat,lon unit is set to display in the raw format + */ + public boolean isRawUnitActive() + { + if (latUnit instanceof LatUnit) + return false; + + return true; + } + + /** + * Returns whether the active lat,lon unit will display seconds. This is true only for properly configured active + * standard units. + */ + public boolean isSecondsShown() + { + if (latUnit instanceof LatUnit) + return ((LatUnit)latUnit).isSecondsShown(); + + return false; + } + + @Override + public String getConfigName() + { + if (latUnit instanceof LatUnit) + return "Standard"; + + return "Raw"; + } + + @Override + public Unit getUnit() + { + throw new RuntimeException("Please use getLatUnit() or getLonUnit() instead."); + } + + @Override + public void zioReadRaw(ZinStream aStream) throws IOException + { + int decimalPlaces; + boolean isRawUnit, isZeroCentered, isSecondsShown; + + // Read the stream's content + aStream.readVersion(0); + + isRawUnit = aStream.readBool(); + decimalPlaces = aStream.readInt(); + isZeroCentered = aStream.readBool(); + isSecondsShown = aStream.readBool(); + + // Install the configuration + if (isRawUnit == true) + activateRaw(decimalPlaces, isZeroCentered); + else + activateStandard(isSecondsShown); + } + + @Override + public void zioWriteRaw(ZoutStream aStream) throws IOException + { + int decimalPlaces; + boolean isRawUnit, isZeroCentered, isSecondsShown; + + // Retrieve the configuration + isRawUnit = isRawUnitActive(); + decimalPlaces = getDecimalPlaces(); + isZeroCentered = isZeroCentered(); + isSecondsShown = isSecondsShown(); + + // Write the stream's contents + aStream.writeVersion(0); + + aStream.writeBool(isRawUnit); + aStream.writeInt(decimalPlaces); + aStream.writeBool(isZeroCentered); + aStream.writeBool(isSecondsShown); + } + +} diff --git a/src/glum/unit/LatUnit.java b/src/glum/unit/LatUnit.java new file mode 100644 index 0000000..6228e79 --- /dev/null +++ b/src/glum/unit/LatUnit.java @@ -0,0 +1,94 @@ +package glum.unit; + +import java.text.*; + +import glum.coord.*; + +public class LatUnit implements Unit +{ + // State vars + private boolean isSecondsShown; + + /** + * Constructor + */ + public LatUnit(boolean aIsSecondsShown) + { + isSecondsShown = aIsSecondsShown; + } + + /** + * isSecondsShown + */ + public boolean isSecondsShown() + { + return isSecondsShown; + } + + @Override + public String getConfigName() + { + return "Lat"; + } + + @Override + public Format getFormat() + { + return null; + } + + @Override + public String getLabel(boolean isDetailed) + { + return ""; + } + + @Override + public String getString(Object aObj) + { + Double aVal; + + if (aObj instanceof Number) + aVal = ((Number)aObj).doubleValue(); +/* else if (aObj instanceof LatLon) + aVal = ((LatLon)aVal).lat; +*/ else + return "N/A"; + + return CoordUtil.LatToString(aVal, isSecondsShown); + } + + @Override + public String getString(Object aObj, boolean isDetailed) + { + Double aVal; + + if (aObj instanceof Number) + aVal = ((Number)aObj).doubleValue(); +/* else if (aObj instanceof LatLon) + aVal = ((LatLon)aVal).lat; +*/ else + return "N/A"; + + return CoordUtil.LatToString(aVal, isSecondsShown); + } + + @Override + public double parseString(String aStr, double eVal) + { + return CoordUtil.StringToLat(aStr); + } + + @Override + public double toModel(double aVal) + { + return aVal; + } + + @Override + public double toUnit(double aVal) + { + return aVal; + } + +} diff --git a/src/glum/unit/LonUnit.java b/src/glum/unit/LonUnit.java new file mode 100644 index 0000000..eb1caca --- /dev/null +++ b/src/glum/unit/LonUnit.java @@ -0,0 +1,94 @@ +package glum.unit; + +import java.text.*; + +import glum.coord.*; + +public class LonUnit implements Unit +{ + // State vars + private boolean isSecondsShown; + + /** + * Constructor + */ + public LonUnit(boolean aIsSecondsShown) + { + isSecondsShown = aIsSecondsShown; + } + + /** + * isSecondsShown + */ + public boolean isSecondsShown() + { + return isSecondsShown; + } + + @Override + public String getConfigName() + { + return "Lon"; + } + + @Override + public Format getFormat() + { + return null; + } + + @Override + public String getLabel(boolean isDetailed) + { + return ""; + } + + @Override + public String getString(Object aObj) + { + Double aVal; + + if (aObj instanceof Number) + aVal = ((Number)aObj).doubleValue(); +/* else if (aObj instanceof LatLon) + aVal = ((LatLon)aVal).lon; +*/ else + return "N/A"; + + return CoordUtil.LonToString(aVal, isSecondsShown); + } + + @Override + public String getString(Object aObj, boolean isDetailed) + { + Double aVal; + + if (aObj instanceof Number) + aVal = ((Number)aObj).doubleValue(); +/* else if (aObj instanceof LatLon) + aVal = ((LatLon)aVal).lon; +*/ else + return "N/A"; + + return CoordUtil.LonToString(aVal, isSecondsShown); + } + + @Override + public double parseString(String aStr, double eVal) + { + return CoordUtil.StringToLon(aStr); + } + + @Override + public double toModel(double aVal) + { + return aVal; + } + + @Override + public double toUnit(double aVal) + { + return aVal; + } + +} diff --git a/src/glum/unit/NumberInverseUnit.java b/src/glum/unit/NumberInverseUnit.java new file mode 100644 index 0000000..7235eb8 --- /dev/null +++ b/src/glum/unit/NumberInverseUnit.java @@ -0,0 +1,64 @@ +package glum.unit; + +import java.text.DecimalFormat;; + +public class NumberInverseUnit extends NumberUnit +{ + public NumberInverseUnit(String aFullLabel, String aShortLabel, double aConversionFactor) + { + super(aFullLabel, aShortLabel, aConversionFactor, (DecimalFormat)null); + } + + public NumberInverseUnit(String aFullLabel, String aShortLabel, double aConversionFactor, int numDecimalPlaces) + { + super(aFullLabel, aShortLabel, aConversionFactor, numDecimalPlaces); + } + + public NumberInverseUnit(String aFullLabel, String aShortLabel, double aConversionFactor, DecimalFormat aFormat) + { + super(aFullLabel, aShortLabel, aConversionFactor, aFormat); + } + + @Override + public String getString(Object aVal) + { + String aStr; + + aStr = "N/A"; + if (aVal instanceof Number == false) + return aStr; + + if (format == null) + return "" + ((1.0/((Number)aVal).doubleValue()) * conversionFactor); + + synchronized (format) + { + return format.format((1.0/((Number)aVal).doubleValue()) * conversionFactor); + } + } + + @Override + public String getString(Object aVal, boolean isDetailed) + { + if (aVal instanceof Number == false) + return "N/A"; + + return "" + ((1.0/((Number)aVal).doubleValue()) * conversionFactor); + } + + @Override + public double toModel(double aVal) + { + if (conversionFactor == 0) + return Double.NaN; + + return 1.0/(aVal / conversionFactor); + } + + @Override + public double toUnit(double aVal) + { + return (1.0/aVal) * conversionFactor; + } + +} diff --git a/src/glum/unit/NumberUnit.java b/src/glum/unit/NumberUnit.java new file mode 100644 index 0000000..cab78ff --- /dev/null +++ b/src/glum/unit/NumberUnit.java @@ -0,0 +1,180 @@ +package glum.unit; + +import glum.gui.GuiUtil; + +import java.text.*; + +public class NumberUnit implements Unit +{ + // State vars + protected DecimalFormat format; + protected String nanStr; + protected String fullLabel; + protected String shortLabel; + protected double conversionFactor; + + /** + * Constructor + */ + public NumberUnit(String aFullLabel, String aShortLabel, double aConversionFactor) + { + this(aFullLabel, aShortLabel, aConversionFactor, (DecimalFormat)null); + } + + public NumberUnit(String aFullLabel, String aShortLabel, double aConversionFactor, String aDecimalFormatStr) + { + this(aFullLabel, aShortLabel, aConversionFactor, new DecimalFormat(aDecimalFormatStr)); + } + + public NumberUnit(String aFullLabel, String aShortLabel, double aConversionFactor, DecimalFormat aFormat) + { + nanStr = "---"; + fullLabel = aFullLabel; + shortLabel = aShortLabel; + conversionFactor = aConversionFactor; + format = aFormat; + } + + public NumberUnit(String aFullLabel, String aShortLabel, double aConversionFactor, int numDecimalPlaces) + { + String aStr; + + nanStr = "---"; + fullLabel = aFullLabel; + shortLabel = aShortLabel; + conversionFactor = aConversionFactor; + + aStr = "#0"; + if (numDecimalPlaces > 0) + { + aStr = "#0."; + for (int c1 = 0; c1 < numDecimalPlaces; c1++) + aStr += "0"; + } + format = new DecimalFormat(aStr); + } + + /** + * Returns whether this unit supports floating point numbers + */ + public boolean isFloating() + { + if (format != null && format.getMaximumFractionDigits() == 0) + return false; + + return true; + +//System.out.println("NumFracDigits:" + format.getMaximumFractionDigits()); +// return format.isParseIntegerOnly(); + } + + /** + * Sets in the string representation for NaN + */ + public void setNaNString(String aStr) + { + nanStr = aStr; + } + + @Override + public Format getFormat() + { + if (format == null) + return null; + + return (Format)format.clone(); + } + + @Override + public String getConfigName() + { + return fullLabel; + } + + @Override + public String getLabel(boolean isDetailed) + { + if (isDetailed == true) + return fullLabel; + else + return shortLabel; + } + + @Override + public String getString(Object aVal) + { + String aStr; + + aStr = "N/A"; + if (aVal instanceof Number == false) + return aStr; + + if (format == null) + return "" + (((Number)aVal).doubleValue() * conversionFactor); + + synchronized (format) + { + return format.format(((Number)aVal).doubleValue() * conversionFactor); + } + } + + @Override + public String getString(Object aVal, boolean isDetailed) + { + String aStr; + double aDouble; + + if (aVal instanceof Number == false) + return nanStr; + + aDouble = ((Number)aVal).doubleValue(); + if (Double.isNaN(aDouble) == true) + return nanStr; + + // Format the number + if (format == null) + { + aStr = "" + aDouble * conversionFactor; + } + else + { + synchronized (format) + { + aStr = format.format(aDouble * conversionFactor); + } + } + + // Add the label component + if (isDetailed == true) + aStr += " " + fullLabel; + else + aStr += " " + shortLabel; + + return aStr; + } + + @Override + public double parseString(String aStr, double eVal) + { + double aVal; + + aVal = GuiUtil.readDouble(aStr, Double.NaN); + return toModel(aVal); + } + + @Override + public double toModel(double aVal) + { + if (conversionFactor == 0) + return Double.NaN; + + return aVal / conversionFactor; + } + + @Override + public double toUnit(double aVal) + { + return aVal * conversionFactor; + } + +} diff --git a/src/glum/unit/ShiftedUnit.java b/src/glum/unit/ShiftedUnit.java new file mode 100644 index 0000000..e06aa3d --- /dev/null +++ b/src/glum/unit/ShiftedUnit.java @@ -0,0 +1,138 @@ +package glum.unit; + +import java.text.*; + +public class ShiftedUnit implements Unit +{ + // State vars + private Format format; + private String nanStr; + private String fullLabel; + private String shortLabel; + private double deltaValue; + + /** + * Constructor + */ + public ShiftedUnit(String aFullLabel, String aShortLabel, double aDeltaValue, Format aFormat) + { + nanStr = "---"; + fullLabel = aFullLabel; + shortLabel = aShortLabel; + deltaValue = aDeltaValue; + format = aFormat; + } + public ShiftedUnit(String aFullLabel, String aShortLabel, double aDeltaValue, int numDecimalPlaces) + { + String aStr; + + aStr = "#0"; + if (numDecimalPlaces > 0) + { + aStr = "#0."; + for (int c1 = 0; c1 < numDecimalPlaces; c1++) + aStr += "0"; + } + + nanStr = "---"; + fullLabel = aFullLabel; + shortLabel = aShortLabel; + deltaValue = aDeltaValue; + format = new DecimalFormat(aStr); + } + + @Override + public String getConfigName() + { + return "shifted"; + } + + @Override + public Format getFormat() + { + if (format == null) + return null; + + return (Format)format.clone(); + } + + @Override + public String getLabel(boolean isDetailed) + { + if (isDetailed == true) + return fullLabel; + else + return shortLabel; + } + + @Override + public String getString(Object aObj) + { + double aVal; + + // We need a number + if (aObj instanceof Number == false) + return "N/A"; + + aVal = ((Number)aObj).doubleValue(); + if (Double.isNaN(aVal) == true) + return nanStr; + + if (format == null) + return "" + (aVal + deltaValue); + + synchronized (format) + { + return format.format(Double.valueOf(aVal + deltaValue)); + } + } + + @Override + public String getString(Object aObj, boolean isDetailed) + { + double aVal; + + // We need a number + if (aObj instanceof Number == false) + return "N/A"; + + aVal = ((Number)aObj).doubleValue(); + if (Double.isNaN(aVal) == true) + return nanStr; + + if (format == null) + { + if (isDetailed == true) + return "" + (aVal + deltaValue) + " " + fullLabel; + else + return "" + (aVal + deltaValue) + " " + shortLabel; + } + + synchronized (format) + { + if (isDetailed == true) + return format.format(Double.valueOf(aVal + deltaValue)) + " " + fullLabel; + else + return format.format(Double.valueOf(aVal + deltaValue)) + " " + shortLabel; + } + } + + @Override + public double parseString(String aStr, double eVal) + { + throw new UnsupportedOperationException(); + } + + @Override + public double toModel(double aVal) + { + return aVal - deltaValue; + } + + @Override + public double toUnit(double aVal) + { + return aVal + deltaValue; + } + +} diff --git a/src/glum/unit/TimeCountUnit.java b/src/glum/unit/TimeCountUnit.java new file mode 100644 index 0000000..d84c573 --- /dev/null +++ b/src/glum/unit/TimeCountUnit.java @@ -0,0 +1,63 @@ +package glum.unit; + +import static glum.util.TimeConst.*; +import glum.util.WallTimer; + +/** + * Unit used to display a total count of time. This Unit is not configurable and only numerical values should be passed + * in where each increment represents 1 millisecond. If a WallTimer is passed in then this Unit will display it's total + * time. + */ +public class TimeCountUnit extends HeuristicUnit +{ + // Constants + private static final long MAX_SEC_BEFORE_FMT_DAY = 2 * MS_IN_DAY; + private static final long MAX_SEC_BEFORE_FMT_HOUR = 2 * MS_IN_HOUR; + private static final long MAX_SEC_BEFORE_FMT_MIN = 2 * MS_IN_MIN; + + // State vars + private String nanStr; + + public TimeCountUnit(int numDecimalPlaces) + { + super("Heuristic", numDecimalPlaces); + nanStr = "---"; + } + + @Override + public String getString(Object aObj) + { + double numMS; + String aStr; + + // Transform WallTimers to their total count + if (aObj instanceof WallTimer) + aObj = ((WallTimer)aObj).getTotal(); + + // We need a number + if (aObj instanceof Number == false) + return "N/A"; + + numMS = ((Number)aObj).doubleValue(); + if (Double.isNaN(numMS) == true) + return nanStr; + + if (numMS > MAX_SEC_BEFORE_FMT_DAY) + aStr = format.format(numMS / MS_IN_DAY) + " days"; + else if (numMS > MAX_SEC_BEFORE_FMT_HOUR) + aStr = format.format(numMS / MS_IN_HOUR) + " hrs"; + else if (numMS > MAX_SEC_BEFORE_FMT_MIN) + aStr = format.format(numMS / MS_IN_MIN) + " min"; + else + aStr = format.format(numMS / MS_IN_SEC) + " sec"; + + return aStr; + } + + @Override + public HeuristicUnit spawnClone(int numDecimalPlaces) + { + return new TimeCountUnit(numDecimalPlaces); + } + +} diff --git a/src/glum/unit/Unit.java b/src/glum/unit/Unit.java new file mode 100644 index 0000000..793474a --- /dev/null +++ b/src/glum/unit/Unit.java @@ -0,0 +1,52 @@ +package glum.unit; + +import java.text.*; + +public interface Unit +{ + /** + * Returns the formal name associated with the unit + */ + public String getConfigName(); + + /** + * Returns Format object associated with the unit. + */ + public Format getFormat(); + + /** + * Returns the label associated with the unit. + */ + public String getLabel(boolean isDetailed); + + /** + * Returns string representation of aVal w.r.t this unit without the + * associated label. + */ + public String getString(Object aVal); + + /** + * Returns string representation of aVal w.r.t this unit with the associated + * (detailed if isDetailed == true) label. + */ + public String getString(Object aVal, boolean isDetailed); + + /** + * Returns the model value which corresponds to the specified input string. + * The input string should be in this unit. If no value can be parsed then + * eVal is returned. + */ + public double parseString(String aStr, double eVal); + + /** + * Assume aVal is in units of this and returns it as model units. + */ + public double toModel(double aVal); + + /** + * Returns aVal in terms of this unit. Note aVal is assumed to be in model + * units. + */ + public double toUnit(double aVal); + +} diff --git a/src/glum/unit/UnitListener.java b/src/glum/unit/UnitListener.java new file mode 100644 index 0000000..48d6986 --- /dev/null +++ b/src/glum/unit/UnitListener.java @@ -0,0 +1,10 @@ +package glum.unit; + +public interface UnitListener +{ + /** + * Event callback for notification of whenever a Unit changes in aManager + */ + public void unitChanged(UnitProvider aProvider, String aKey); + +} diff --git a/src/glum/unit/UnitProvider.java b/src/glum/unit/UnitProvider.java new file mode 100644 index 0000000..22b8752 --- /dev/null +++ b/src/glum/unit/UnitProvider.java @@ -0,0 +1,32 @@ +package glum.unit; + +import glum.zio.raw.ZioRaw; + +public interface UnitProvider extends ZioRaw +{ + /** + * Adds a Listener for Unit changes + */ + public void addListener(UnitListener aListener); + + /** + * Removes a Listener for Unit changes + */ + public void removeListener(UnitListener aListener); + + /** + * Returns the name of the current configuration + */ + public String getConfigName(); + + /** + * Returns the official name name associated with the Unit + */ + public String getDisplayName(); + + /** + * Returns the Unit associated with this provider + */ + public Unit getUnit(); + +} diff --git a/src/glum/util/DateUtil.java b/src/glum/util/DateUtil.java new file mode 100644 index 0000000..abed8c9 --- /dev/null +++ b/src/glum/util/DateUtil.java @@ -0,0 +1,152 @@ +package glum.util; + +import java.util.*; + +public class DateUtil +{ + public final static int SECONDS_IN_DAY = 60 * 60 * 24; + public final static int MINUTES_IN_DAY = 60 * 24; + + /** + * Returns the date as a double that best describes the currDate position relative to startDate and endDate. + *

    + * Return should be between 0 - 1. + */ + public static double computeFractionalPosition(Calendar startDate, Calendar currDate, Calendar endDate) + { + double currTimeSpan, totTimeSpan; + + // Insanity checks + if (startDate == null || currDate == null || endDate == null) + return 0; + + if (startDate.getTime().after(endDate.getTime()) == true) + return 0; + + if (currDate.getTime().before(startDate.getTime()) == true) + return 0; + + if (currDate.getTime().after(endDate.getTime()) == true) + return 1; + + totTimeSpan = getTotalSeconds(startDate, endDate); + currTimeSpan = getTotalSeconds(startDate, currDate); + + return currTimeSpan / totTimeSpan; + } + + /** + * Returns the date closest to fracPos relative to sDate and eDate. Note fracPos should range from 0-1 + */ + public static Calendar computeDate(Calendar sDate, Calendar eDate, double fracPos) + { + Calendar currDate; + long offsetSecs; + int deltaMins, deltaSecs; + + // Compute the number of seconds to add + offsetSecs = (long)(getTotalSeconds(sDate, eDate) * fracPos); + + // Compute delta secs and delta min + deltaMins = (int)(offsetSecs / 60); + deltaSecs = (int)(offsetSecs - (deltaMins * 60)); + + // Construct the new target calendar + currDate = (Calendar)sDate.clone(); + currDate.add(Calendar.MINUTE, deltaMins); + currDate.add(Calendar.SECOND, deltaSecs); + + return currDate; + } + + /** + * Returns the total seconds between the start date and end date + */ + public static long getTotalSeconds(Calendar sDate, Calendar eDate) + { + Calendar tmpDate; + int sYear, eYear, cYear; + int sDay, eDay; + int sHour, eHour; + int sMin, eMin; + int sSec, eSec; + long daysLeftStart, daysTillEnd, totNumFullDays; + long secsLeftStart, secsTillStart, secsTillEnd, totNumSecs; + + // Insanity check + if (sDate == null || eDate == null) + return 0; + + // Compute the various components of the start and end date + sYear = sDate.get(Calendar.YEAR); + eYear = eDate.get(Calendar.YEAR); + sDay = sDate.get(Calendar.DAY_OF_YEAR); + eDay = eDate.get(Calendar.DAY_OF_YEAR); + sHour = sDate.get(Calendar.HOUR_OF_DAY); + eHour = eDate.get(Calendar.HOUR_OF_DAY); + sMin = sDate.get(Calendar.MINUTE); + eMin = eDate.get(Calendar.MINUTE); + sSec = sDate.get(Calendar.SECOND); + eSec = eDate.get(Calendar.SECOND); + + // Get the days left in the year of the current startDate + daysLeftStart = sDate.getActualMaximum(Calendar.DAY_OF_YEAR) - sDay; + + // Get the days that have passed in the year of the endDate + daysTillEnd = eDay - eDate.getActualMinimum(Calendar.DAY_OF_YEAR); + + // Add the days to the total number of days + if (sYear == eYear) + totNumFullDays = (eDay - sDay) - 1; + else + totNumFullDays = daysLeftStart + daysTillEnd; + + // Add the total number of days from the years in between the start and end date + tmpDate = new GregorianCalendar(sYear, 0, 1); + for (cYear = sYear + 1; cYear < eYear; cYear++) + { + tmpDate.set(Calendar.YEAR, cYear); + totNumFullDays += tmpDate.getActualMaximum(Calendar.DAY_OF_YEAR); + } + + // Get the number of seconds left in the current day of the startDate + secsLeftStart = SECONDS_IN_DAY - (sHour * 3600 + sMin * 60 + sSec); + + // Get the number of seconds that have passed in the current day of the startDate + secsTillStart = sHour * 3600 + sMin * 60 + sSec; + + // Get the number of seconds that have passed in the current day of the endDate + secsTillEnd = eHour * 3600 + eMin * 60 + eSec; + + // Compute the total number of full seconds + if (sYear == eYear && sDay == eDay) + totNumSecs = secsTillEnd - secsTillStart; + else + totNumSecs = secsLeftStart + secsTillEnd + (totNumFullDays * SECONDS_IN_DAY); + + return totNumSecs; + } + + /** + * Returns aDate if it is bounded by sDate and eDate; else it returns the nearest boundary date. + */ + public static Calendar verifyDateBounds(Calendar sDate, Calendar eDate, Calendar aDate) + { + Date sTime, eTime, aTime; + + if (sDate == null || eDate == null || aDate == null) + return aDate; + + sTime = sDate.getTime(); + eTime = eDate.getTime(); + aTime = aDate.getTime(); + + if (aTime.compareTo(sTime) < 0) + return (Calendar)sDate.clone(); + else if (aTime.compareTo(eTime) > 0) + return (Calendar)eDate.clone(); + + return aDate; + } + +} diff --git a/src/glum/util/ImageUtil.java b/src/glum/util/ImageUtil.java new file mode 100644 index 0000000..c0c580f --- /dev/null +++ b/src/glum/util/ImageUtil.java @@ -0,0 +1,25 @@ +package glum.util; + +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; + +public class ImageUtil +{ + + /** + * Utility method to return the Image associated with the specified resource. + */ + public static BufferedImage getImageForResource(String imagePath) + { + try + { + return ImageIO.read(ClassLoader.getSystemResource(imagePath)); + } + catch(Exception aExp) + { + throw new RuntimeException("Failed to load resource " + imagePath, aExp); + } + } + +} diff --git a/src/glum/util/MathUtil.java b/src/glum/util/MathUtil.java new file mode 100644 index 0000000..67d865b --- /dev/null +++ b/src/glum/util/MathUtil.java @@ -0,0 +1,125 @@ +package glum.util; + +import java.awt.Dimension; +import java.awt.Point; + +public class MathUtil +{ + /** + * Utility method that returns aVal to be clamped within the range of minVal or maxVal. + */ + public static double boundRange(double minVal, double maxVal, double aVal) + { + if (aVal < minVal) + return minVal; + + if (aVal > maxVal) + return maxVal; + + return aVal; + } + + /** + * Utility method that returns aVal to be clamped within the range of minVal or maxVal. + */ + public static long boundRange(long minVal, long maxVal, long aVal) + { + if (aVal < minVal) + return minVal; + + if (aVal > maxVal) + return maxVal; + + return aVal; + } + + /** + * Utility method that returns aVal to be clamped within the range of minVal or maxVal. + */ + public static int boundRange(int minVal, int maxVal, int aVal) + { + if (aVal < minVal) + return minVal; + + if (aVal > maxVal) + return maxVal; + + return aVal; + } + + /** + * Utility method to ensure that a window defined by targLoc and targDim will be fully in the area defined by the + * bound of (x0,y0 - x1,y1). The targLoc and targDim will be modified to fit the constraints. Note the window will be + * made no smaller than minDim. If it is impossible to honor the constraints then no modifications will be made. + * + * @param tryKeepDim + * True, if preservation of dimension is more important than preservation of location. + */ + public static void forceConstraints(Point targLoc, Dimension targDim, int x0, int y0, int x1, int y1, Dimension minDim, boolean tryKeepDim) + { + int boundW, boundH; + int eX, eY; + + // Bail if the constraints are nonsensical + boundW = x1 - x0; + boundH = y1 - y0; + if (minDim.width > boundW || minDim.height > boundH) + return; + + // Move the panel towards the northwest corner if any part are overhanging the southeast region + if (tryKeepDim == true) + { + // Ensure the southeast location is fully contained in the specified bounds + if (targLoc.x + targDim.width > x1) + targLoc.x = x1 - targDim.width; + + if (targLoc.y + targDim.height > y1) + targLoc.y = y1 - targDim.height; + } + + // Ensure the northwest location is fully contained in the specified bounds + if (targLoc.x < x0) + { + targDim.width -= x0 - targLoc.x; + targLoc.x = x0; + } + + if (targLoc.y < y0) + { + targDim.height -= y0 - targLoc.y; + targLoc.y = y0; + } + + // Ensure the targDim is no bigger than the bounded area + if (targDim.width > boundW) + { + targDim.width = boundW; + } + + if (targDim.height > boundH) + { + targDim.height = boundH; + } + + // Ensure the minimum dimension is honored + if (targDim.width < minDim.width) + { + eX = minDim.width - targDim.width; + targDim.width = minDim.width; + + if (targLoc.x > x0) + targLoc.x -= eX; + } + + if (targDim.height < minDim.height) + { + eY = minDim.height - targDim.height; + targDim.height = minDim.height; + + if (targLoc.y > y0) + targLoc.y -= eY; + } + + } + +} diff --git a/src/glum/util/ThreadUtil.java b/src/glum/util/ThreadUtil.java new file mode 100644 index 0000000..0216f11 --- /dev/null +++ b/src/glum/util/ThreadUtil.java @@ -0,0 +1,205 @@ +package glum.util; + +import glum.task.Task; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; + +import javax.swing.SwingUtilities; + +public class ThreadUtil +{ + + /** + * Utility method to execute aRunnable synchronously on the AWT event dispatching thread with out throwing an + * InterruptedException. If the thread is interrupted however the stack trace will be printed. This method will still + * throw an InvocationTargetException but encapsulated in a RuntimeException. + */ + public static void invokeAndWaitOnAwt(Runnable aRunnable) + { + while (true) + { + try + { + SwingUtilities.invokeAndWait(aRunnable); + break; + } + catch(InterruptedException aExp) + { + aExp.printStackTrace(); + } + catch(InvocationTargetException aExp) + { + // This is an unrecoverable exception + throw new RuntimeException(aExp); + } + } + } + + /** + * Utility method to launch a Runnable in a new Thread + */ + public static void launchRunnable(Runnable aRunnable, String threadName) + { + Thread aThread; + + aThread = new Thread(aRunnable, threadName); + aThread.start(); + } + + /** + * Utility method to suspend the current thread for numMS milliseconds. + * + * @param aTask + * This method will return prematurely if aTask is no longer active. + */ + public static void safeSleep(long numMS, Task aTask) + { + long wakeTime; + + wakeTime = System.currentTimeMillis() + numMS; + sleepUntilTime(wakeTime, aTask); + } + + /** + * Utility method to sleep for numMS milliseconds without throwing an {@link InterruptedException}. + * + * @return True: if the thread was interrupted + */ + public static boolean safeSleep(long numMS) + { + try + { + Thread.sleep(numMS); + } + catch(InterruptedException aExp) + { + return true; + } + return false; + } + + /** + * Utility method to wait for a signal without throwing an {@link InterruptedException}. + * + * @see Thread#wait() + * + * @return True: if the thread was interrupted + */ + public static boolean safeWait(Object aLock) + { + try + { + aLock.wait(); + } + catch(InterruptedException aExp) + { + return true; + } + return false; + } + + /** + * Utility method to wait for a signal without throwing an {@link InterruptedException}. + * + * @see Thread#wait(long) + * + * @return True: if the thread was interrupted + */ + public static boolean safeWait(Object aLock, long aMaxTimeMS) + { + try + { + aLock.wait(aMaxTimeMS); + } + catch(InterruptedException aExp) + { + return true; + } + return false; + } + + /** + * Utility method to suspend the current thread until the system time is at or has passed nextWakeTime + * + * @return True: if the thread was interrupted + */ + public static boolean sleepUntilTime(long nextWakeTime) + { + long currTime, sleepTime; + boolean isInterrupt; + + currTime = System.currentTimeMillis(); + while (currTime < nextWakeTime) + { + sleepTime = nextWakeTime - currTime; + + isInterrupt = ThreadUtil.safeSleep(sleepTime); + if (isInterrupt == true) + return true; + + currTime = System.currentTimeMillis(); + } + + return false; + } + + /** + * Utility method to suspend the current thread until the system time is at or has passed nextWakeTime + * + * @param aTask + * This method will return prematurely if aTask is no longer active. + */ + public static void sleepUntilTime(long nextWakeTime, Task aTask) + { + long currTime, sleepTime; + + currTime = System.currentTimeMillis(); + while (currTime < nextWakeTime && aTask.isActive() == true) + { + sleepTime = nextWakeTime - currTime; + ThreadUtil.safeSleep(sleepTime); + + currTime = System.currentTimeMillis(); + } + } + + /** + * Utility method to print the stack trace of aExp to a string + */ + public static String getStackTrace(Throwable aThrowable) + { + StringBuilder strBuf; + + strBuf = new StringBuilder(); + strBuf.append(aThrowable.getClass().getName() + "\n"); + strBuf.append("Msg: " + aThrowable.getMessage() + "\n"); + + // Print out the stack trace + for (StackTraceElement aItem : aThrowable.getStackTrace()) + strBuf.append(" " + aItem.toString() + "\n"); + + // Print out any cause + // TODO + + return strBuf.toString(); + } + + /** + * Utility method to print the stack trace of aExp to a string exactly as {@link Throwable#printStackTrace} + */ + public static String getStackTraceClassic(Throwable aThrowable) + { + StringWriter stringWriter; + PrintWriter printWriter; + + stringWriter = new StringWriter(); + printWriter = new PrintWriter(stringWriter); + aThrowable.printStackTrace(printWriter); + + printWriter.close(); + return stringWriter.toString(); + } + +} diff --git a/src/glum/util/TimeConst.java b/src/glum/util/TimeConst.java new file mode 100644 index 0000000..280423b --- /dev/null +++ b/src/glum/util/TimeConst.java @@ -0,0 +1,11 @@ +package glum.util; + +public class TimeConst +{ + // Constants + public static final long MS_IN_SEC = 1000; + public static final long MS_IN_MIN = 60 * MS_IN_SEC; + public static final long MS_IN_HOUR = 60 * MS_IN_MIN; + public static final long MS_IN_DAY = 24 * MS_IN_HOUR; + +} diff --git a/src/glum/util/WallTimer.java b/src/glum/util/WallTimer.java new file mode 100644 index 0000000..f70d381 --- /dev/null +++ b/src/glum/util/WallTimer.java @@ -0,0 +1,87 @@ +package glum.util; + +public class WallTimer +{ + private long startTime, stopTime; + + public WallTimer() + { + this(false); + } + + public WallTimer(boolean startNow) + { + startTime = 0; + stopTime = 0; + + if (startNow == true) + start(); + } + + /** + * Returns the start time + */ + public long getStartTime() + { + return startTime; + } + + /** + * Returns the stop time + */ + public long getStopTime() + { + return stopTime; + } + + /** + * Starts the timer + */ + public void start() + { + startTime = System.currentTimeMillis(); + stopTime = 0; + } + + /** + * Stops the timer + */ + public void stop() + { + stopTime = System.currentTimeMillis(); + } + + /** + * Returns the total number of seconds since the timer has been started. If it has been stopped then the returned + * value is the total time between start and stop. + * + * TODO: Phase this method out and use getTotal() + */ + public double getTotalSec() + { + return getTotal() / 1000.0; + } + + /** + * Returns the total number of milliseconds since the timer has been started. If it has been stopped then the + * returned value is the total time between start and stop. + */ + public long getTotal() + { + long aTime; + + // Return 0 if we have not been started + if (startTime == 0) + return 0; + + // Return the total since stopped + if (stopTime != 0) + return stopTime - startTime; + + // If the timer has not been stopped then return + // the current running time + aTime = System.currentTimeMillis(); + return aTime - startTime; + } + +} diff --git a/src/glum/zio/BaseZinStream.java b/src/glum/zio/BaseZinStream.java new file mode 100644 index 0000000..7ea73c4 --- /dev/null +++ b/src/glum/zio/BaseZinStream.java @@ -0,0 +1,433 @@ +package glum.zio; + +import glum.util.WallTimer; +import glum.zio.raw.ZioRaw; +import glum.zio.util.ZioUtil; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; + +public abstract class BaseZinStream implements ZinStream +{ + // Work vars + protected ByteBuffer workBuffer; + + // Stat vars + private WallTimer wallTimer; + private MessageDigest digest; + private String checkSumStr; + protected int digestPos; + + /** + * @param computeCheckSum + * True if a checksum (md5sum) is desired to be computed as the stream is written + * @param streamSizeHint + * A hint which indicates the final size of the source stream. This hint will be used to determine if a + * direct buffer should be allocated. If the hint size is greater than 25 MB then a direct buffer will be + * allocated. A value of 0 implies that a direct buffer should not be allocated. + */ + public BaseZinStream(boolean computeCheckSum, long streamSizeHint) throws IOException + { + // Allocate the stat vars + wallTimer = new WallTimer(true); + digest = null; + checkSumStr = null; + digestPos = -1; + if (computeCheckSum == true) + { + try + { + digest = MessageDigest.getInstance("MD5"); + digestPos = 0; + } + catch (NoSuchAlgorithmException aExp) + { + throw new IOException("Unreconized Algorithm", aExp); + } + } + + // Allocate our work vars + allocateWorkVars(streamSizeHint); + } + + /** + * @param aWorkBuffer + * This ByteBuffer will be used as the workBuffer. No workBuffer will be allocated for this BaseZinStream. + * @param computeCheckSum + * True if a checksum (md5sum) is desired to be computed as the stream is written + */ + public BaseZinStream(ByteBuffer aWorkBuffer, boolean computeCheckSum) throws IOException + { + // Allocate the checksum digest worker + digest = null; + checkSumStr = null; + if (computeCheckSum == true) + { + try + { + digest = MessageDigest.getInstance("MD5"); + digestPos = 0; + } + catch (NoSuchAlgorithmException aExp) + { + throw new IOException("Unreconized Algorithm", aExp); + } + } + + // Allocate our work vars + workBuffer = aWorkBuffer; + if (workBuffer == null) + throw new NullPointerException(); + } + + /** + * Returns the length of time (in milliseconds) this stream has been open + */ + public long getRunTime() + { + return wallTimer.getTotal(); + } + + @Override + public void close() throws IOException + { + // Bail if we have already been closed + if (workBuffer == null) + return; + + // Stop the timer + wallTimer.stop(); + + // Force the checksum to be computed + getCheckSum(); + + // Release the work and digest vars + workBuffer = null; + digest = null; + digestPos = -1; + + // Release the stream vars + releaseStreamVars(); + } + + @Override + public String getCheckSum() throws IOException + { + // If the stream is dead then just return the previously computed checksum + if (workBuffer == null || digest == null) + return checkSumStr; + + // We are on a live stream, and need to (re)evaluate the digest + updateDigest(); + + // Transform the digest into a checksum + checkSumStr = new BigInteger(1, digest.digest()).toString(16); + checkSumStr = Strings.padStart(checkSumStr, 32, '0'); + return checkSumStr; + } + + @Override + public byte readByte() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 1) + refreshWorkBuffer(); + + return workBuffer.get(); + } + + @Override + public boolean readBool() throws IOException + { + return readByte() != 0; + } + + @Override + public char readChar() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 2) + refreshWorkBuffer(); + + return workBuffer.getChar(); + } + + @Override + public int readInt() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 4) + refreshWorkBuffer(); + + return workBuffer.getInt(); + } + + @Override + public long readLong() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 8) + refreshWorkBuffer(); + + return workBuffer.getLong(); + } + + @Override + public short readShort() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 2) + refreshWorkBuffer(); + + return workBuffer.getShort(); + } + + @Override + public float readFloat() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 4) + refreshWorkBuffer(); + + return workBuffer.getFloat(); + } + + @Override + public double readDouble() throws IOException + { + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 8) + refreshWorkBuffer(); + + return workBuffer.getDouble(); + } + + @Override + public String readString() throws IOException + { + byte[] data; + int size; + + size = readShort() & 0x00FFFF; + if (size == 0x00FFFF) + return null; + if (size == 0) + return ""; + + data = new byte[size]; + readFully(data); + return new String(data, Charsets.UTF_8); + } + + @Override + public void readRawStringAndValidate(String absStr) throws IOException + { + byte[] absByteArr, readByteArr; + + // Transform the passed in string to US-ASCII + absByteArr = absStr.getBytes(Charsets.US_ASCII); + + // Grab the next set of bytes from the stream equal to length of absByteArr + readByteArr = new byte[absStr.length()]; + readFully(readByteArr); + + // Ensure the two arrays are equal + if (Arrays.equals(absByteArr, readByteArr) == false) + throw new IOException("Mismatched string. Needed:" + absStr + " Found:" + new String(readByteArr, Charsets.US_ASCII)); + } + + @Override + public void readFully(byte[] dstArr, int offset, int length) throws IOException + { + int bytesRead, numToRead; + + bytesRead = 0; + while (bytesRead != length) + { + numToRead = workBuffer.remaining(); + if (bytesRead + numToRead > length) + numToRead = length - bytesRead; + + workBuffer.get(dstArr, offset + bytesRead, numToRead); + bytesRead += numToRead; + + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 1 && bytesRead < length) + refreshWorkBuffer(); + } + } + + @Override + public void readFully(byte[] dstArr) throws IOException + { + int bytesRead, numToRead; + int length; + + bytesRead = 0; + length = dstArr.length; + while (bytesRead != length) + { + numToRead = workBuffer.remaining(); + if (bytesRead + numToRead > length) + numToRead = length - bytesRead; + + workBuffer.get(dstArr, bytesRead, numToRead); + bytesRead += numToRead; + + // Ensure there is enough data in workBuffer + if (workBuffer.remaining() < 1 && bytesRead < length) + refreshWorkBuffer(); + } + } + + @Override + public int readVersion(int aValidVer) throws IOException + { + int readVersion; + + // Read the version + readVersion = readByte() & 0x00FF; + if (readVersion == 255) + readVersion = readInt(); + + // Ensure the version is one of the valid versions + if (readVersion == aValidVer) + return readVersion; + + // Failure, let the user know of the version that was read vs what is valid + throw new IOException("Unreconized version... Read: " + readVersion + " Expected: " + aValidVer); + } + + @Override + public int readVersion(int... validArr) throws IOException + { + int readVersion; + + // Read the version + readVersion = ZioUtil.readCompactInt(this); + + // Ensure the version is one of the valid versions + for (int aVersion : validArr) + { + if (aVersion == readVersion) + return aVersion; + } + + // Failure, let the user know of the version that was read vs what is valid + if (validArr.length == 1) + throw new IOException("Unreconized version... Read: " + readVersion + " Expected: " + validArr[0]); + + throw new IOException("Unreconized version... Read: " + readVersion + " Expected one of the following: " + Arrays.toString(validArr)); + } + + @Override + public void readZioRaw(ZioRaw aZioRaw) throws IOException + { + aZioRaw.zioReadRaw(this); + } + + @Override + public void skipBytes(int numBytes) throws IOException + { + int bytesSkipped, numToSkip; + + bytesSkipped = 0; + while (bytesSkipped != numBytes) + { + numToSkip = workBuffer.remaining(); + if (bytesSkipped + numToSkip > numBytes) + numToSkip = numBytes - bytesSkipped; + + workBuffer.position(workBuffer.position() + numToSkip); + bytesSkipped += numToSkip; + + // Ensure there is enough data in workBuffer (to skip) + if (workBuffer.remaining() < 1 && bytesSkipped < numBytes) + refreshWorkBuffer(); + } + } + + /** + * Helper method to refresh the workBuffer with new data from the stream. This method ensures that workBuffer will + * always have enough data to support reading. + *

    + * If there is no more data on the stream then this method should throw an IOException + */ + protected abstract void refreshWorkBuffer() throws IOException; + + /** + * Helper method to release any stream related vars. This method will only be called once, the very first time the + * method {@link #close()} is called. + */ + protected abstract void releaseStreamVars() throws IOException; + + /** + * Helper method that ensures the digest has been updated with any data that has been "read" thus far. The definition + * of "read" is any data returned from the stream via one of the read methods. The digest shall not be updated with + * any buffered data - only data that has been read from the stream. + */ + protected void updateDigest() throws IOException + { + ByteBuffer tmpBuffer; + + // Bail if the there is no digest + if (digest == null) + return; + + // Retrieve a duplicate of the workBuffer (to preserve its configuration) + tmpBuffer = workBuffer.duplicate(); + + // Evaluate the digest from the digestPos to the limit (workBuffer's current position) + tmpBuffer.flip(); + tmpBuffer.position(digestPos); + digest.update(tmpBuffer); + + // Update the digest position + digestPos = tmpBuffer.limit(); + } + + /** + * Helper method to allocate our work vars. + */ + private void allocateWorkVars(long streamSizeHint) throws IOException + { + int workCap; + boolean isDirect; + + // Determine if we should use a direct buffer for our workBuffer (stream > 25 MB) + isDirect = false; + if (streamSizeHint > 25 * 1024 * 1024) + isDirect = true; + + // Allocate our byte buffer + if (isDirect == false) + { + // [1K, 16K], indirect buffer + workCap = (int)streamSizeHint; + if (workCap < 1024) + workCap = 1024; + else if (workCap > 16 * 1024) + workCap = 16 * 1024; + + workBuffer = ByteBuffer.allocate(workCap); + } + else + { + // 512K, direct buffer + workCap = 512 * 1024; + workBuffer = ByteBuffer.allocateDirect(workCap); + } +//System.out.println("Is direct buffer: " + workBuffer.isDirect() + " bufferCap: " + workCap); + + // Mark the contents in workBuffer as completely empty + workBuffer.limit(0); + } + +} diff --git a/src/glum/zio/BaseZoutStream.java b/src/glum/zio/BaseZoutStream.java new file mode 100644 index 0000000..b2d1014 --- /dev/null +++ b/src/glum/zio/BaseZoutStream.java @@ -0,0 +1,336 @@ +package glum.zio; + +import glum.util.WallTimer; +import glum.zio.raw.ZioRaw; +import glum.zio.util.ZioUtil; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; + +public abstract class BaseZoutStream implements ZoutStream +{ + // Work vars + protected ByteBuffer workBuffer; + + // Stat vars + private WallTimer wallTimer; + private MessageDigest digest; + private String checkSumStr; + + /** + * @param computeCheckSum + * True if a checksum (md5sum) is desired to be computed as the stream is written + * @param isDirect + * True if a direct buffer is desired. This should only be true if the stream is going to a physical I/O + * component (disk, network) and the size of the final stream will be at least ~50 MB. + */ + public BaseZoutStream(boolean computeCheckSum, boolean isDirect) throws IOException + { + // Allocate the stat vars + wallTimer = new WallTimer(true); + digest = null; + checkSumStr = null; + try + { + if (computeCheckSum == true) + digest = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException aExp) + { + throw new IOException("Unreconized Algorithm", aExp); + } + + // Allocate our work vars + allocateWorkVars(isDirect); + } + + /** + * Returns the length of time (in milliseconds) this stream has been open + */ + public long getRunTime() + { + return wallTimer.getTotal(); + } + + @Override + public void close() throws IOException + { + // Bail if we have already been closed + if (workBuffer == null) + return; + + // Empty any remaining buffered bytes + emptyWorkBuffer(); + + // Force the checksum to be computed + getCheckSum(); + + // Release the work and digest vars + workBuffer = null; + digest = null; + + // Release the stream vars + releaseStreamVars(); + } + + @Override + public String getCheckSum() throws IOException + { + // If the stream is dead then just return the previously computed checksum + if (workBuffer == null || digest == null) + return checkSumStr; + + // We are on a live stream, and need to (re)evaluate the digest + // Ensure there are no unaccounted bytes in the workBuffer + emptyWorkBuffer(); + + // Transform the digest into a checksum + checkSumStr = new BigInteger(1, digest.digest()).toString(16); + checkSumStr = Strings.padStart(checkSumStr, 32, '0'); + return checkSumStr; + } + + @Override + public void writeByte(byte aByte) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 1) + emptyWorkBuffer(); + + workBuffer.put(aByte); + } + + @Override + public void writeBool(boolean aBool) throws IOException + { + if (aBool == false) + writeByte((byte)0); + else + writeByte((byte)1); + } + + @Override + public void writeChar(char aChar) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 2) + emptyWorkBuffer(); + + workBuffer.putChar(aChar); + } + + @Override + public void writeInt(int aInt) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 4) + emptyWorkBuffer(); + + workBuffer.putInt(aInt); + } + + @Override + public void writeLong(long aLong) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 8) + emptyWorkBuffer(); + + workBuffer.putLong(aLong); + } + + @Override + public void writeShort(short aShort) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 2) + emptyWorkBuffer(); + + workBuffer.putShort(aShort); + } + + @Override + public void writeFloat(float aFloat) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 4) + emptyWorkBuffer(); + + workBuffer.putFloat(aFloat); + } + + @Override + public void writeDouble(double aDouble) throws IOException + { + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 8) + emptyWorkBuffer(); + + workBuffer.putDouble(aDouble); + } + + @Override + public void writeString(String aStr) throws IOException + { + byte[] data; + int size; + + // Null strings are handled in special fashion + if (aStr == null) + { + writeShort((short)0x00FFFF); + return; + } + + // Empty strings are handled in special fashion + if (aStr.equals("") == true) + { + writeShort((short)0); + return; + } + + // Transform the string to it's UTF-8 bytes + data = aStr.getBytes(Charsets.UTF_8); + size = data.length; + + // Ensure the string size is less than 0x00FFFF + if (size >= 0x00FFFF) + throw new RuntimeException("Transformed UTF-8 string is too large! Max size: " + (0x00FFFF - 1) + " Curr size:" + size); + + // Write out the string + writeShort((short)(size & 0x00FFFF)); + writeFully(data); + } + + @Override + public void writeRawString(String aStr) throws IOException + { + byte[] byteArr; + + byteArr = aStr.getBytes(Charsets.US_ASCII); + writeFully(byteArr); + } + + @Override + public void writeFully(byte[] dstArr, int offset, int length) throws IOException + { + int bytesWritten, numToWrite; + + bytesWritten = 0; + while (bytesWritten != length) + { + numToWrite = workBuffer.remaining(); + if (bytesWritten + numToWrite > length) + numToWrite = length - bytesWritten; + + workBuffer.put(dstArr, offset + bytesWritten, numToWrite); + bytesWritten += numToWrite; + + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 1 && bytesWritten < length) + emptyWorkBuffer(); + } + } + + @Override + public void writeFully(byte[] dstArr) throws IOException + { +// writeFully(dstArr, 0, dstArr.length); + int bytesWritten, numToWrite; + int length; + + bytesWritten = 0; + length = dstArr.length; + while (bytesWritten != length) + { + numToWrite = workBuffer.remaining(); + if (bytesWritten + numToWrite > length) + numToWrite = length - bytesWritten; + + workBuffer.put(dstArr, bytesWritten, numToWrite); + bytesWritten += numToWrite; + + // Ensure there is enough space in workBuffer + if (workBuffer.remaining() < 1 && bytesWritten < length) + emptyWorkBuffer(); + } + } + + @Override + public void writeVersion(int aVersion) throws IOException + { + ZioUtil.writeCompactInt(this, aVersion); + } + + @Override + public void writeZioRaw(ZioRaw aZioRaw) throws IOException + { + aZioRaw.zioWriteRaw(this); + } + + /** + * Helper method that ensures the digest has been updated with any buffered data. The buffer will be cleared after + * the digest has been updated. + *

    + * The method shall be called exclusively from {@link BaseZoutStream#emptyWorkBuffer()}. + */ + protected void clearWorkBuffer() + { + // Update the digest (if requested) + if (digest != null) + { + workBuffer.rewind(); + digest.update(workBuffer); + } + + // Clear the workBuffer + workBuffer.clear(); + } + + /** + * Helper method to empty the workBuffer and copy the contents to the stream. The contents of the workBuffer will be + * output to the "stream". This method ensures that workBuffer will always have enough data to support writing + */ + protected abstract void emptyWorkBuffer() throws IOException; + + /** + * Helper method to release any stream related vars. This method will only be called once, the very first time the + * method {@link #close()} is called. + */ + protected abstract void releaseStreamVars() throws IOException; + + /** + * Helper method to allocate our work vars. + * + * @throws IOException + */ + private void allocateWorkVars(boolean isDirect) throws IOException + { + int workCap; + + // Allocate our byte buffer + if (isDirect == false) + { + // 16K, indirect buffer + workCap = 16 * 1024; + workBuffer = ByteBuffer.allocate(workCap); + } + else + { + // 512K, direct buffer + workCap = 512 * 1024; + workBuffer = ByteBuffer.allocateDirect(workCap); + } +//System.out.println("Is direct buffer: " + workBuffer.isDirect() + " bufferCap: " + workCap); + + // Mark the buffers as empty + workBuffer.clear(); + } + +} diff --git a/src/glum/zio/ByteArrayZinStream.java b/src/glum/zio/ByteArrayZinStream.java new file mode 100644 index 0000000..e05c913 --- /dev/null +++ b/src/glum/zio/ByteArrayZinStream.java @@ -0,0 +1,54 @@ +package glum.zio; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class ByteArrayZinStream extends BaseZinStream +{ + public ByteArrayZinStream(byte[] aDataArr, boolean computeCheckSum) throws IOException + { + super(ByteBuffer.wrap(aDataArr), computeCheckSum); + + // Move the position to the start + workBuffer.rewind(); + } + + public ByteArrayZinStream(byte[] aDataArr) throws IOException + { + this(aDataArr, false); + } + + @Override + public long getAvailable() throws IOException + { + if (workBuffer == null) + return 0; + + return workBuffer.remaining(); + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return workBuffer.position(); + } + + @Override + protected void refreshWorkBuffer() throws IOException + { + // There will never be new fresh data for a ByteArrayZinStream + throw new EOFException("EOF reached on stream."); + } + + @Override + protected void releaseStreamVars() throws IOException + { + ; // Nothing to do + } + +} diff --git a/src/glum/zio/ByteArrayZoutStream.java b/src/glum/zio/ByteArrayZoutStream.java new file mode 100644 index 0000000..478acaf --- /dev/null +++ b/src/glum/zio/ByteArrayZoutStream.java @@ -0,0 +1,87 @@ +package glum.zio; + +import java.io.IOException; +import java.util.Arrays; + +public class ByteArrayZoutStream extends BaseZoutStream +{ + // Stream vars + private byte[] dataArr; + private int dataPos; + + public ByteArrayZoutStream(int initCap, boolean computeCheckSum) throws IOException + { + super(computeCheckSum, false); + + // Set up the stream vars + dataArr = new byte[initCap]; + dataPos = 0; + } + + public ByteArrayZoutStream(int initCap) throws IOException + { + this(initCap, false); + } + + /** + * Returns a byte array of the contents of this stream. + */ + public byte[] toByteArray() + { + // This is a live stream, so make sure the buffer has been emptied + if (workBuffer != null) + { + emptyWorkBuffer(); + return Arrays.copyOf(dataArr, dataPos); + } + + // Return the final byte array + return dataArr; + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return dataPos + workBuffer.position(); + } + + @Override + protected void emptyWorkBuffer() + { + byte[] newArr; + int numBufBytes; + + // Prepare the buffer for a dump of its contents from the start + workBuffer.flip(); + + // Ensure there is enough space in dataArr + numBufBytes = workBuffer.remaining(); + if (dataArr.length - dataPos < numBufBytes) + { + newArr = new byte[(dataArr.length + numBufBytes) * 2]; + + System.arraycopy(dataArr, 0, newArr, 0, dataPos); + dataArr = newArr; + } + + // Copy the contents of workBuffer to the stream (dataArr) + workBuffer.get(dataArr, dataPos, numBufBytes); + dataPos += numBufBytes; + + // Clear the workBuffer + clearWorkBuffer(); + } + + @Override + protected void releaseStreamVars() throws IOException + { + // Form the final data byte array + dataArr = Arrays.copyOf(dataArr, dataPos); + dataPos = -1; + } + +} diff --git a/src/glum/zio/FileZinStream.java b/src/glum/zio/FileZinStream.java new file mode 100644 index 0000000..910ac32 --- /dev/null +++ b/src/glum/zio/FileZinStream.java @@ -0,0 +1,89 @@ +package glum.zio; + +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; + +public class FileZinStream extends BaseZinStream +{ + // Stream vars + private FileChannel fileCh; + private byte[] staleArr; + + public FileZinStream(File aFile, boolean computeCheckSum) throws IOException + { + super(computeCheckSum, aFile.length()); + + // Set up the stream vars + fileCh = new FileInputStream(aFile).getChannel(); + staleArr = new byte[256]; + } + + public FileZinStream(File aFile) throws IOException + { + this(aFile, false); + } + + @Override + public long getAvailable() throws IOException + { + if (workBuffer == null) + return 0; + + return fileCh.size() - (fileCh.position() - workBuffer.remaining()); + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return fileCh.position() - workBuffer.remaining(); + } + + @Override + protected void refreshWorkBuffer() throws IOException + { + int numReadBytes, numStaleBytes; + + // Ensure the digest has been updated before refreshing the buffer + updateDigest(); + + // Copies the remaining data from workBuffer to a byte (stale) array + numStaleBytes = workBuffer.remaining(); + if (numStaleBytes > 0) + workBuffer.get(staleArr, 0, numStaleBytes); + + // Clear the buffer and copy the (stale) bytes from the byte array to the start of workBuffer + workBuffer.clear(); + if (numStaleBytes > 0) + workBuffer.put(staleArr, 0, numStaleBytes); + + // Fill the remaining workBuffer with data from the "stream" + numReadBytes = fileCh.read(workBuffer); + if (numReadBytes == 0) + System.out.println("Failed to read any buffer bytes!!! Bytes formerly read: " + numReadBytes); + if (numReadBytes == -1) + throw new EOFException("EOF reached on stream."); + + // Mark the buffer as fully prepared and ready for processing + workBuffer.flip(); + + // Mark the current digestPos to the start of the workBuffer + digestPos = 0; + } + + @Override + protected void releaseStreamVars() throws IOException + { + fileCh.close(); + + fileCh = null; + staleArr = null; + } + +} diff --git a/src/glum/zio/FileZoutStream.java b/src/glum/zio/FileZoutStream.java new file mode 100644 index 0000000..ebca0c1 --- /dev/null +++ b/src/glum/zio/FileZoutStream.java @@ -0,0 +1,64 @@ +package glum.zio; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; + +public class FileZoutStream extends BaseZoutStream +{ + // Stream vars + private FileChannel fileCh; + + public FileZoutStream(File aFile, boolean computeCheckSum, boolean isDirect) throws IOException + { + super(computeCheckSum, isDirect); + + // Set up the stream vars + fileCh = new FileOutputStream(aFile).getChannel(); + } + + public FileZoutStream(File aFile) throws IOException + { + this(aFile, false, false); + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return fileCh.position() + workBuffer.position(); + } + + @Override + protected void emptyWorkBuffer() throws IOException + { + int bytesWritten; + + // Prepare the buffer for a dump of its contents from the start + workBuffer.flip(); + + // Copy the contents of workBuffer to the stream (fileCh) + bytesWritten = 0; + while (workBuffer.remaining() > 0) + { + bytesWritten = fileCh.write(workBuffer); + if (workBuffer.remaining() > 0) + System.out.println("Failed to write buffer all at once. bytesToWrite: " + workBuffer.remaining() + ". Bytes formerly written: " + bytesWritten); + } + + // Clear the workBuffer + clearWorkBuffer(); + } + + @Override + protected void releaseStreamVars() throws IOException + { + fileCh.close(); + fileCh = null; + } + +} diff --git a/src/glum/zio/NullZoutStream.java b/src/glum/zio/NullZoutStream.java new file mode 100644 index 0000000..89004c4 --- /dev/null +++ b/src/glum/zio/NullZoutStream.java @@ -0,0 +1,159 @@ +package glum.zio; + +import glum.zio.raw.ZioRaw; + +import java.io.IOException; + +import com.google.common.base.Charsets; + +/** + * ZoutStream used to count bytes that would be written. + */ +public class NullZoutStream implements ZoutStream +{ + private int byteCount; + + public NullZoutStream() + { + byteCount = 0; + } + + /** + * Returns the number of bytes that have been written + */ + public int getNumBytes() + { + return byteCount; + } + + @Override + public void close() throws IOException + { + throw new IOException("Unsupported operation."); + } + + @Override + public String getCheckSum() throws IOException + { + throw new IOException("Unsupported operation."); + } + + @Override + public long getPosition() throws IOException + { + return byteCount; + } + + @Override + public void writeByte(byte aByte) throws IOException + { + byteCount++; + } + + @Override + public void writeBool(boolean aBool) throws IOException + { + byteCount++; + } + + @Override + public void writeChar(char aChar) throws IOException + { + byteCount += 2; + } + + @Override + public void writeInt(int aInt) throws IOException + { + byteCount += 4; + } + + @Override + public void writeLong(long aLong) throws IOException + { + byteCount += 8; + } + + @Override + public void writeShort(short aShort) throws IOException + { + byteCount += 2; + } + + @Override + public void writeFloat(float aFloat) throws IOException + { + byteCount += 4; + } + + @Override + public void writeDouble(double aDouble) throws IOException + { + byteCount += 8; + } + + @Override + public void writeString(String aStr) throws IOException + { + byte[] data; + int size; + + // Null strings are handled in special fashion + if (aStr == null) + { + byteCount += 2; + return; + } + + // Empty strings are handled in special fashion + if (aStr.equals("") == true) + { + byteCount += 2; + return; + } + + // Transform the string to it's UTF-8 bytes + data = aStr.getBytes(Charsets.UTF_8); + size = data.length; + + // Ensure the string size is less than 0x00FFFF + if (size >= 0x00FFFF) + throw new RuntimeException("Transformed UTF-8 string is too large! Max size: " + (0x00FFFF - 1) + " Curr size:" + size); + + byteCount += 2 + size; + } + + @Override + public void writeRawString(String aStr) throws IOException + { + byteCount += aStr.length(); + } + + @Override + public void writeFully(byte[] dstArr, int offset, int length) throws IOException + { + byteCount += length; + } + + @Override + public void writeFully(byte[] dstArr) throws IOException + { + byteCount += dstArr.length; + } + + @Override + public void writeVersion(int aVersion) throws IOException + { + if (aVersion < 255) + byteCount++; + else + byteCount += 5; + } + + @Override + public void writeZioRaw(ZioRaw aZioRaw) throws IOException + { + aZioRaw.zioWriteRaw(this); + } + +} diff --git a/src/glum/zio/ZinStream.java b/src/glum/zio/ZinStream.java new file mode 100644 index 0000000..7ee1073 --- /dev/null +++ b/src/glum/zio/ZinStream.java @@ -0,0 +1,133 @@ +package glum.zio; + +import glum.zio.raw.ZioRaw; + +import java.io.IOException; + +public interface ZinStream +{ + /** + * Releases any resources associated with the ZioInStream + */ + public void close() throws IOException; + + /** + * Returns the number of bytes still available in the input stream + */ + public long getAvailable() throws IOException; + + /** + * Returns the checksum (as a string) of the stream. + *

    + * Note, if the stream is still open, then the returned value will be the checksum evaluated as of the last byte + * grabbed from this stream (with no buffering effects - closing the stream immediately will not result in a + * different value). + */ + public String getCheckSum() throws IOException; + + /** + * Returns the virtual position of this stream + * + * @throws IOException + * If the stream has been closed. + */ + public long getPosition() throws IOException; + + /** + * Returns the next byte + */ + public byte readByte() throws IOException; + + /** + * Returns the next byte (cast to a boolean) + */ + public boolean readBool() throws IOException; + + /** + * Returns the next char + */ + public char readChar() throws IOException; + + /** + * Returns the next int + */ + public int readInt() throws IOException; + + /** + * Returns the next long + */ + public long readLong() throws IOException; + + /** + * Returns the next short + */ + public short readShort() throws IOException; + + /** + * Returns the next float + */ + public float readFloat() throws IOException; + + /** + * Returns the next double + */ + public double readDouble() throws IOException; + + /** + * Reads an 8-bit UTF-8 string from aStream. First 2 bytes specify length of string. + */ + public String readString() throws IOException; + + /** + * Method to read in a raw string and validate that it matches the passed in absStr. This method is useful to ensure + * that file headers and the like are are valid. Note the inverse function is {@link ZoutStream#writeRawString}. The + * string will be interpreted as a US-ASCII string. + */ + public void readRawStringAndValidate(String absStr) throws IOException; + + /** + * Returns the contents of dstArr, starting from offset to length fully. + * + * @throws IOException + * Will be thrown if not enough data in the stream to fulfill request + */ + public void readFully(byte[] dstArr, int offset, int length) throws IOException; + + /** + * Reads in the contents of dstArr fully. + * + * @throws IOException + * Will be thrown if not enough data in the stream to fulfill request + */ + public void readFully(byte[] dstArr) throws IOException; + + /** + * Method to read the recorded version, and validate that it matches aValidVer. If there is a mismatch, then an + * IOException will be thrown. The recorded version should have been written with the inverse method + * {@link ZoutStream#writeVersion}. + */ + public int readVersion(int aValidVer) throws IOException; + + /** + * Method to read the recorded version, and validate that it matches one of the values in validArr. If there is a + * mismatch, then an IOException will be thrown. The recorded version should have been written with the inverse + * method {@link ZoutStream#writeVersion}. + * + * @return The version that was read in. + */ + public int readVersion(int... validArr) throws IOException; + + /** + * Reads in the ZioRaw object from this stream + */ + public void readZioRaw(ZioRaw aBZioRaw) throws IOException; + + /** + * Method to skip numBytes. + * + * @throws IOException + * Will be thrown if not enough data in the stream to fulfill request + */ + public void skipBytes(int numBytes) throws IOException; + +} diff --git a/src/glum/zio/ZoutStream.java b/src/glum/zio/ZoutStream.java new file mode 100644 index 0000000..360ea04 --- /dev/null +++ b/src/glum/zio/ZoutStream.java @@ -0,0 +1,108 @@ +package glum.zio; + +import glum.zio.raw.ZioRaw; + +import java.io.IOException; + +public interface ZoutStream +{ + /** + * Releases any resources associated with the stream + */ + public void close() throws IOException; + + /** + * Returns the checksum (as a string) of the stream. + *

    + * Note, if the stream is still open, then the returned value will be the checksum evaluated as of the last byte sent + * to this stream (with no buffering effects - closing the stream immediately will not result in a different value). + */ + public String getCheckSum() throws IOException; + + /** + * Returns the virtual position of this stream. + * + * @throws IOException + * If the stream has been closed. + */ + public long getPosition() throws IOException; + + /** + * Outputs the next byte + */ + public void writeByte(byte aByte) throws IOException; + + /** + * Outputs the next byte (cast from a boolean) + */ + public void writeBool(boolean aBool) throws IOException; + + /** + * Outputs the next char + */ + public void writeChar(char aChar) throws IOException; + + /** + * Outputs the next int + */ + public void writeInt(int aInt) throws IOException; + + /** + * Outputs the next long + */ + public void writeLong(long aLong) throws IOException; + + /** + * Outputs the next short + */ + public void writeShort(short aShort) throws IOException; + + /** + * Outputs the next float + */ + public void writeFloat(float aFloat) throws IOException; + + /** + * Outputs the next double + */ + public void writeDouble(double aDouble) throws IOException; + + /** + * Writes an 8-bit UTF string to aStream. First 2 bytes specify length of string. + */ + public void writeString(String aStr) throws IOException; + + /** + * Utility method to write out a raw string. Note the inverse function is {@link ZinStream#readRawStringAndValidate}. + * The string will be interpreted as a US-ASCII string. + */ + public void writeRawString(String aStr) throws IOException; + + /** + * Writes the contents of dstArr, starting from offset to length fully. + * + * @throws IOException + * Will be thrown if not enough space in the stream to fulfill request + */ + public void writeFully(byte[] dstArr, int offset, int length) throws IOException; + + /** + * Writes the contents of dstArr fully. + * + * @throws IOException + * Will be thrown if not enough space in the stream to fulfill request + */ + public void writeFully(byte[] dstArr) throws IOException; + + /** + * Method to write the version to the stream. To properly read the version, use the inverse function + * {@link ZinStream#readVersion}. + */ + public void writeVersion(int aVersion) throws IOException; + + /** + * Writes the ZioRaw object to this stream + */ + public void writeZioRaw(ZioRaw aBinRaw) throws IOException; + +} diff --git a/src/glum/zio/raw/ZioRaw.java b/src/glum/zio/raw/ZioRaw.java new file mode 100644 index 0000000..b9876ac --- /dev/null +++ b/src/glum/zio/raw/ZioRaw.java @@ -0,0 +1,23 @@ +package glum.zio.raw; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; + +/** + * Interface to allow for simple serialization of binary objects. + */ +public interface ZioRaw +{ + /** + * Deserialization method to read data from the ZinStream + */ + public void zioReadRaw(ZinStream aStream) throws IOException; + + /** + * Serialization method to write data to the ZoutStream + */ + public void zioWriteRaw(ZoutStream aStream) throws IOException; + +} diff --git a/src/glum/zio/raw/ZioRawUtil.java b/src/glum/zio/raw/ZioRawUtil.java new file mode 100644 index 0000000..160c1c4 --- /dev/null +++ b/src/glum/zio/raw/ZioRawUtil.java @@ -0,0 +1,222 @@ +package glum.zio.raw; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ZioRawUtil +{ + /** + * Utility method to read a list of BinRaw items. The objects are assumed to be of the same type. + *

    + * Format: ()* + */ + public static ArrayList readRawList(ZinStream aStream, Class binClass) throws IOException + { + ArrayList itemList; + G1 aItem; + int numItems; + + // Read the item count + numItems = aStream.readInt(); + + // Read the actual objects + itemList = new ArrayList(); + for (int c1 = 0; c1 < numItems; c1++) + { + // Serialize the class + try + { + aItem = binClass.newInstance(); + aItem.zioReadRaw(aStream); + itemList.add(aItem); + } + catch (Exception aException) + { + throw new IOException("Failed to instantiate: " + binClass, aException); + } + } + + return itemList; + } + + /** + * Utility method to read a preloaded list of BinRaw items. The passed in list must contain the exact number of items + * as that stored on disk and in the correct order. + *

    + * Format: ()* + */ + public static void readRawList(ZinStream aStream, Collection itemList) throws IOException + { + int numItems; + + // Read the item count + numItems = aStream.readInt(); + if (numItems != itemList.size()) + throw new IOException("Items stored: " + numItems + ". Expected: " + itemList.size()); + + // Read the actual BinRaw items + for (ZioRaw aItem : itemList) + aItem.zioReadRaw(aStream); + } + + /** + * Utility method to write out a list of BinRaw items. + *

    + * Format: ()* + */ + public static void writeRawList(ZoutStream aStream, Collection itemList) throws IOException + { + // Write the item count + aStream.writeInt(itemList.size()); + + // Write the actual objects + for (ZioRaw aItem : itemList) + aItem.zioWriteRaw(aStream); + } + + /** + * Utility method to read a map of binary objects. The BinRaw items are assumed to be of the same type. + *

    + * Format: ()* + */ + public static Map readRawMap(ZinStream aStream, Class binClass) throws IOException + { + Map itemMap; + String aKey; + G1 aItem; + int numItems; + + // Read the item count + numItems = aStream.readInt(); + + // Read the actual objects + itemMap = new LinkedHashMap(); + for (int c1 = 0; c1 < numItems; c1++) + { + // Read the key + aKey = aStream.readString(); + + // Read the value + aItem = readRaw(aStream, binClass); + itemMap.put(aKey, aItem); + } + + return itemMap; + } + + /** + * Utility method to read a map of raw binary items. The items are assumed to be preloaded. Thus the passed in map + * must contain as many items (and in the order) as that which will be read in from the disk. It is therefore + * advisable that only LinkedHashMaps be used with this method. + */ + public static void readRawMap(ZinStream aStream, Map itemMap) throws IOException + { + String[] keyArr; + String aKey; + ZioRaw aItem; + int numItems; + + // Read the item count + numItems = aStream.readInt(); + if (numItems != itemMap.size()) + throw new IOException("Items stored: " + numItems + ". Expected: " + itemMap.size()); + + // Read the actual key,value pairings + keyArr = itemMap.keySet().toArray(new String[0]); + for (int c1 = 0; c1 < numItems; c1++) + { + // Read the key and ensure the proper key was read + aKey = aStream.readString(); + if (aKey.equals(keyArr[c1]) == false) + throw new IOException("Key read: " + aKey + ". Expected: " + keyArr[c1]); + + // Read the value + aItem = itemMap.get(aKey); + aItem.zioReadRaw(aStream); + } + } + + /** + * Utility method to write out a map of binary raw items. + *

    + * Format: ()* + */ + public static void writeRawMap(ZoutStream aStream, Map itemMap) throws IOException + { + ZioRaw aBinObj; + + // Write the item count + aStream.writeInt(itemMap.size()); + + // Write the actual objects + for (String aKey : itemMap.keySet()) + { + // Write the key + aStream.writeString(aKey); + + // Write the value + aBinObj = itemMap.get(aKey); + aBinObj.zioWriteRaw(aStream); + } + } + + public static G1 readRaw(ZinStream aStream, Class binClass) throws IOException + { + G1 aItem; + + // Serialize the class + try + { + aItem = binClass.newInstance(); + aItem.zioReadRaw(aStream); + } + catch (Exception aException) + { + throw new IOException("Failed to instantiate: " + binClass, aException); + } + + return aItem; + } + + public static G1 readNullableRaw(ZinStream aStream, Class binClass) throws IOException + { + boolean aBool; + G1 aItem; + + aBool = aStream.readBool(); + if (aBool == false) + return null; + + // Serialize the class + try + { + aItem = binClass.newInstance(); + aItem.zioReadRaw(aStream); + } + catch (Exception aException) + { + throw new IOException("Failed to instantiate: " + binClass, aException); + } + + return aItem; + } + + public static void writeNullableRaw(ZoutStream aStream, ZioRaw aItem) throws IOException + { + if (aItem == null) + { + aStream.writeBool(false); + return; + } + + aStream.writeBool(true); + aItem.zioWriteRaw(aStream); + } + +} diff --git a/src/glum/zio/util/WrapInputStream.java b/src/glum/zio/util/WrapInputStream.java new file mode 100644 index 0000000..38f3524 --- /dev/null +++ b/src/glum/zio/util/WrapInputStream.java @@ -0,0 +1,94 @@ +package glum.zio.util; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +import glum.zio.BaseZinStream; + +/** + * Package private class to transform an InputStream to a ZinStream (view). + */ +class WrapInputStream extends BaseZinStream +{ + // Stream vars + private ReadableByteChannel refCh; + private byte[] staleArr; + private long streamPos; + + protected WrapInputStream(InputStream aStream, boolean computeCheckSum) throws IOException + { + super(computeCheckSum, 0); + + // Set up the stream vars + refCh = Channels.newChannel(aStream); + staleArr = new byte[256]; + streamPos = 0; + } + + protected WrapInputStream(InputStream aStream) throws IOException + { + this(aStream, false); + } + + @Override + public long getAvailable() throws IOException + { + throw new IOException("Unsupported operation"); + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return streamPos - workBuffer.remaining(); + } + + @Override + protected void refreshWorkBuffer() throws IOException + { + int numReadBytes, numStaleBytes; + + // Ensure the digest has been updated before refreshing the buffer + updateDigest(); + + // Copies the remaining data from workBuffer to a byte (stale) array + numStaleBytes = workBuffer.remaining(); + if (numStaleBytes > 0) + workBuffer.get(staleArr, 0, numStaleBytes); + + // Clear the buffer and copy the (stale) bytes from the byte array to the start of workBuffer + workBuffer.clear(); + if (numStaleBytes > 0) + workBuffer.put(staleArr, 0, numStaleBytes); + + // Fill the remaining workBuffer with data from the "stream" + numReadBytes = refCh.read(workBuffer); + if (numReadBytes == 0) + System.out.println("Failed to read any buffer bytes!!! Bytes formerly read: " + numReadBytes); + if (numReadBytes == -1) + throw new EOFException("EOF reached on stream."); + + // Mark the buffer as fully prepared and ready for processing + workBuffer.flip(); + + // Mark the current digestPos to the start of the workBuffer + digestPos = 0; + } + + @Override + protected void releaseStreamVars() throws IOException + { + refCh.close(); + + refCh = null; + staleArr = null; + streamPos = -1; + } + +} diff --git a/src/glum/zio/util/WrapOutputStream.java b/src/glum/zio/util/WrapOutputStream.java new file mode 100644 index 0000000..f84feef --- /dev/null +++ b/src/glum/zio/util/WrapOutputStream.java @@ -0,0 +1,73 @@ +package glum.zio.util; + +import glum.zio.BaseZoutStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; + +/** + * Package private class to transform an OutputStream to a ZoutStream (view). + */ +public class WrapOutputStream extends BaseZoutStream +{ + // Stream vars + private WritableByteChannel refCh; + private long streamPos; + + protected WrapOutputStream(OutputStream aStream, boolean computeCheckSum, boolean isDirect) throws IOException + { + super(computeCheckSum, isDirect); + + // Set up the stream vars + refCh = Channels.newChannel(aStream); + streamPos = 0; + } + + protected WrapOutputStream(OutputStream aStream) throws IOException + { + this(aStream, false, false); + } + + @Override + public long getPosition() throws IOException + { + // There is no virtual position if the stream has been closed + if (workBuffer == null) + throw new IOException("Stream has been closed."); + + return streamPos + workBuffer.remaining(); + } + + @Override + protected void emptyWorkBuffer() throws IOException + { + int bytesWritten; + + // Prepare the buffer for a dump of its contents from the start + workBuffer.flip(); + + // Copy the contents of workBuffer to the stream (fileCh) + bytesWritten = 0; + while (workBuffer.remaining() > 0) + { + bytesWritten = refCh.write(workBuffer); + if (workBuffer.remaining() > 0) + System.out.println("Failed to write buffer all at once. bytesToWrite: " + workBuffer.remaining() + ". Bytes formerly written: " + bytesWritten); + } + + // Clear the workBuffer + clearWorkBuffer(); + } + + @Override + protected void releaseStreamVars() throws IOException + { + refCh.close(); + + refCh = null; + streamPos = -1; + } + +} diff --git a/src/glum/zio/util/WrapZinStream.java b/src/glum/zio/util/WrapZinStream.java new file mode 100644 index 0000000..acb800c --- /dev/null +++ b/src/glum/zio/util/WrapZinStream.java @@ -0,0 +1,67 @@ +package glum.zio.util; + +import glum.zio.ZinStream; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Package private class to transform a ZinStream to an InputStream (view). + */ +class WrapZinStream extends InputStream +{ + private ZinStream refStream; + + protected WrapZinStream(ZinStream aStream) + { + refStream = aStream; + } + + @Override + public void close() throws IOException + { + super.close(); + refStream.close(); + } + + @Override + public int read() throws IOException + { + if (refStream.getAvailable() == 0) + return -1; + + return 0x00FF & refStream.readByte(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + long availBytes; + + availBytes = refStream.getAvailable(); + if (availBytes == 0) + return -1; + + if (len > availBytes) + len = (int)availBytes; + + refStream.readFully(b, off, len); + return len; + } + + @Override + public long skip(long len) throws IOException + { + long availBytes; + + availBytes = refStream.getAvailable(); + if (availBytes == 0) + return -1; + + if (len > availBytes) + len = availBytes; + + refStream.skipBytes((int)len); + return len; + } +} \ No newline at end of file diff --git a/src/glum/zio/util/WrapZoutStream.java b/src/glum/zio/util/WrapZoutStream.java new file mode 100644 index 0000000..8bb4c21 --- /dev/null +++ b/src/glum/zio/util/WrapZoutStream.java @@ -0,0 +1,39 @@ +package glum.zio.util; + +import glum.zio.ZoutStream; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Package private class to transform a ZoutStream to an OutputStream (view). + */ +class WrapZoutStream extends OutputStream +{ + private ZoutStream refStream; + + protected WrapZoutStream(ZoutStream aStream) + { + refStream = aStream; + } + + @Override + public void close() throws IOException + { + super.close(); + refStream.close(); + } + + @Override + public void write(int b) throws IOException + { + refStream.writeByte((byte)(b & 0x00FF)); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + refStream.writeFully(b, off, len); + } + +} \ No newline at end of file diff --git a/src/glum/zio/util/ZioUtil.java b/src/glum/zio/util/ZioUtil.java new file mode 100644 index 0000000..fbae98c --- /dev/null +++ b/src/glum/zio/util/ZioUtil.java @@ -0,0 +1,205 @@ +package glum.zio.util; + +import glum.task.Task; +import glum.unit.TimeCountUnit; +import glum.zio.FileZinStream; +import glum.zio.FileZoutStream; +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.io.IOException; + +public class ZioUtil +{ + /** + * Utility method to print the various stats gathered while reading to the specified task. + */ + public static void printResults(Task aTask, FileZinStream aStream, Object aSource) throws IOException + { + TimeCountUnit aUnit; + long runTime; + String checksum; + String message; + + // Ensure we have a valid task + if (aTask == null) + return; + + aUnit = new TimeCountUnit(3); + checksum = aStream.getCheckSum(); + runTime = aStream.getRunTime(); + message = "[Read] MD5: " + checksum + " Source: " + aSource + " Time: " + aUnit.getString(runTime); + aTask.infoAppendln(message); + } + + /** + * Utility method to print the various stats gathered while writing to the specified task. + */ + public static void printResults(Task aTask, FileZoutStream aStream, Object aSource) throws IOException + { + TimeCountUnit aUnit; + long runTime; + String checksum; + String message; + + // Ensure we have a valid task + if (aTask == null) + return; + + aUnit = new TimeCountUnit(3); + checksum = aStream.getCheckSum(); + runTime = aStream.getRunTime(); + message = "[Read] MD5: " + checksum + " Source: " + aSource + " Time: " + aUnit.getString(runTime); + aTask.infoAppendln(message); + } + + /** + * Utility method to read a compact int. A compact int will take anywhere from 1 byte to 5 bytes of storage. This + * method is the inverse of {@link ZioUtil#writeCompactInt} + */ + public static int readCompactInt(ZinStream aStream) throws IOException + { + int aValue; + + // Read the int + aValue = aStream.readByte() & 0x00FF; + if (aValue == 255) + aValue = aStream.readInt(); + + return aValue; + } + + /** + * Utility method to write a compact int. A compact int will take anywhere from 1 byte to 5 bytes of storage. This + * method is the inverse of {@link ZioUtil#readCompactInt} + */ + public static void writeCompactInt(ZoutStream aStream, int aValue) throws IOException + { + byte byteVal; + + // Takes up one byte if in the range of (0, 254) + byteVal = (byte)0x00FF; + if (aValue >= 0 && aValue < 255) + byteVal = (byte)(0x00FF & aValue); + aStream.writeByte(byteVal); + + // Takes up 5 bytes otherwise + if (aValue >= 255) + aStream.writeInt(aValue); + } + + /** + * Utility method to read a {@link Dimension} from aStream. This method is the inverse of + * {@link ZioUtil#writeDimension} + */ + public static Dimension readDimension(ZinStream aStream) throws IOException + { + Dimension aDimension; + boolean aBool; + + aDimension = null; + aBool = aStream.readBool(); + if (aBool == true) + aDimension = new Dimension(aStream.readInt(), aStream.readInt()); + + return aDimension; + } + + /** + * Utility method to write aDimension to aStream. The argument aDimension may be null. This method is the inverse of + * {@link ZioUtil#readDimension} + */ + public static void writeDimension(ZoutStream aStream, Dimension aDimension) throws IOException + { + boolean aBool; + + aBool = (aDimension != null); + aStream.writeBool(aBool); + if (aBool == true) + { + aStream.writeInt(aDimension.width); + aStream.writeInt(aDimension.height); + } + } + + /** + * Utility method to read a {@link Point} from aStream. This method is the inverse of {@link ZioUtil#writePoint} + */ + public static Point readPoint(ZinStream aStream) throws IOException + { + Point aPoint; + boolean aBool; + + aPoint = null; + aBool = aStream.readBool(); + if (aBool == true) + aPoint = new Point(aStream.readInt(), aStream.readInt()); + + return aPoint; + } + + /** + * Utility method to write aPoint to aStream. The argument aPoint may be null. This method is the inverse of + * {@link ZioUtil#readPoint} + */ + public static void writePoint(ZoutStream aStream, Point aPoint) throws IOException + { + boolean aBool; + + aBool = (aPoint != null); + aStream.writeBool(aBool); + if (aBool == true) + { + aStream.writeInt(aPoint.x); + aStream.writeInt(aPoint.y); + } + } + + /** + * Utility method to read a {@link Color} from aStream. This method is the inverse of {@link ZioUtil#writeColor} + */ + public static Color readColor(ZinStream aStream) throws IOException + { + byte type; + int r, g, b; + + type = aStream.readByte(); + if (type == 0) + return null; + + r = aStream.readByte() & 0x00FF; + g = aStream.readByte() & 0x00FF; + b = aStream.readByte() & 0x00FF; + return new Color(r, g, b); + } + + /** + * Utility method to write aColor to aStream. The argument aColor may be null. This method is the inverse of + * {@link ZioUtil#readColor} + */ + public static void writeColor(ZoutStream aStream, Color aColor) throws IOException + { + byte byteVal; + + if (aColor == null) + { + aStream.writeByte((byte)0); + return; + } + + aStream.writeByte((byte)1); + + byteVal = (byte)(0x00FF & aColor.getRed()); + aStream.writeByte(byteVal); + + byteVal = (byte)(0x00FF & aColor.getGreen()); + aStream.writeByte(byteVal); + + byteVal = (byte)(0x00FF & aColor.getBlue()); + aStream.writeByte(byteVal); + } + +} diff --git a/src/glum/zio/util/ZioWrapUtil.java b/src/glum/zio/util/ZioWrapUtil.java new file mode 100644 index 0000000..c50c08b --- /dev/null +++ b/src/glum/zio/util/ZioWrapUtil.java @@ -0,0 +1,65 @@ +package glum.zio.util; + +import glum.zio.ZinStream; +import glum.zio.ZoutStream; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Utility class that takes a ZinStream/ZoutStream and presents various InputStream/OutputStream views. + */ +public class ZioWrapUtil +{ + /** + * Utility method to return the ZinStream as a DataInputStream view + */ + public static DataInputStream asDataInputStream(ZinStream aStream) + { + return new DataInputStream(asInputStream(aStream)); + } + + /** + * Utility method to return the ZinStream as a DataInputStream view + */ + public static DataOutputStream asDataOutputStream(ZoutStream aStream) + { + return new DataOutputStream(asOutputStream(aStream)); + } + + /** + * Utility method to return the ZinStream as an InputStream view + */ + public static InputStream asInputStream(ZinStream aStream) + { + return new WrapZinStream(aStream); + } + + /** + * Utility method to return the ZoutStream as an OutputStream view + */ + public static OutputStream asOutputStream(ZoutStream aStream) + { + return new WrapZoutStream(aStream); + } + + /** + * Utility method to return InputStream the as a ZinStream view + */ + public static ZinStream asZinStream(InputStream aStream) throws IOException + { + return new WrapInputStream(aStream); + } + + /** + * Utility method to return InputStream the as a ZinStream view + */ + public static ZoutStream asZoutStream(OutputStream aStream) throws IOException + { + return new WrapOutputStream(aStream); + } + +}