//
// @(#)GUIOrderUtils.java 12/2002
//
// Copyright 2002 Zachary DelProposto. All rights reserved.
// Use is subject to license terms.
//
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// Or from http://www.gnu.org/
//
package dip.gui.order;
import dip.world.Location;
import dip.world.Unit;
import dip.world.Phase;
import dip.world.Power;
import dip.world.Province;
import dip.world.Border;
import dip.world.TurnState;
import dip.order.Orderable;
import dip.order.Move;
import dip.order.Hold;
import dip.order.Support;
import dip.misc.Utils;
import dip.gui.map.MapMetadata;
import dip.gui.map.DefaultMapRenderer2;
import dip.gui.map.SVGUtils;
import dip.gui.order.GUIOrder.MapInfo;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.Iterator;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.apache.batik.util.SVGConstants;
import org.apache.batik.util.CSSConstants;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.util.XLinkSupport;
import org.w3c.dom.svg.*;
import org.w3c.dom.*;
/**
* Utility methods for GUIOrder subclasses.
*
*
*
*
*
*
*
*/
final class GUIOrderUtils
{
/** Amount to make DifficultPassableBorder Lines, compared to normal. Should be in the (0, 1.0) range. */
private final static float DPB_LINE_WIDTH = 0.333f;
/**
* Determines if the given border allows a given action.
* <p>
* Please note that this uses the superclass for any passed GUIOrder
* object. (e.g., dip.order.Hold for dip.gui.order.GUIHold)
* <p>
* GUIOrder.BORDER_INVALID is appended to the StringBuffer, if 'false' is returned.
*/
public static boolean checkBorder(GUIOrder guiOrder, Location location, Unit.Type unitType, Phase phase, StringBuffer sb)
{
Class baseClass = guiOrder.getClass().getSuperclass();
Border border = location.getProvince().getTransit(location, unitType, phase, baseClass);
if(border != null)
{
sb.append( Utils.getLocalString(GUIOrder.BORDER_INVALID, guiOrder.getFullName(), border.getDescription()) );
return false;
}
return true;
}// checkBorder()
/** Creates the points for an Octagon about center point of a given radius. */
public static Point2D.Float[] makeOctagon(Point2D.Float center, float radius)
{
// A polygon has 8 sides. All sides are equal lengths. The top/bottom & r/l sides if
// bisected will form a right triangle with angles (67.5, 90, and 22.5). One side is
// the radius; thus the length ('a') of the bisected side of the triangle is:
// a = tan(22.5 deg) * radius.
// thus the top/left/bottom/right sides have a length of 2*a. However, all we need is
// 'a' since we can derive all polygon points once 'a' is known. (they are all a combination
// of (center point +/- radius),(center point +/- a) and (center point +/- a),(center point +/- radius)
//
// compute a:
// note: 22.5 degrees = 360 / 8 / 2. or 2*PI/8/2 = PI/8.
float a = (float) Math.tan(Math.PI/8) * radius;
Point2D.Float[] points = new Point2D.Float[8];
for(int i=0; i<points.length; i++)
{
points[i] = new Point2D.Float();
}
// 8 points starting clockwise; first point would be approximately 1 o'clock position
// this is just more clear then reflection. Precalc could occur if required. But whatever.
points[0].x = center.x + a;
points[0].y = center.y - radius;
points[1].x = center.x + radius;
points[1].y = center.y - a;
points[2].x = center.x + radius;
points[2].y = center.y + a;
points[3].x = center.x + a;
points[3].y = center.y + radius;
points[4].x = center.x - a;
points[4].y = center.y + radius;
points[5].x = center.x - radius;
points[5].y = center.y + a;
points[6].x = center.x - radius;
points[6].y = center.y - a;
points[7].x = center.x - a;
points[7].y = center.y - radius;
// return
return points;
}// makeOctagon()
/** Creates the points for an equilateral Triangle about center point of a given radius. */
public static Point2D.Float[] makeTriangle(Point2D.Float center, float radius)
{
float a = (float) (Math.cos(Math.PI/6) * radius);
float b = (float) (Math.sin(Math.PI/6) * radius);
Point2D.Float[] points = new Point2D.Float[3];
for(int i=0; i<points.length; i++)
{
points[i] = new Point2D.Float();
}
// 3 points starting clockwise at 12'o'clock
points[0].x = center.x;
points[0].y = center.y - radius;
points[1].x = center.x + a;
points[1].y = center.y + b;
points[2].x = center.x - a;
points[2].y = center.y + b;
// return
return points;
}// makeTriangle()
/**
* Respect a Radius
* <p>
* Computes a point-line intersection and returns the result as a Point2D.Float
* object. This returns the intersected point.
* <p>
* x1,y1 and x2,y2 are line coordinates
* x3,y3 and R are the center and Radius of the circle to check for intersection.
*
* <p>
* <b>NOTE:</b> This assumes that the point and line intersect.
* <p>
* If x1,y1 == x2,y2 (this can happen if order verification is lenient) or other
* condition occurs where the internal value mu is NaN, we will return the
* point (x1,y1).
*
*/
public static Point2D.Float getLineCircleIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float r)
{
// how to respect radius:
/*
via circle-line intersection:
line: (x1,y1) to (x2,y2)
circle: radius (r), center (x3,y3)
intersection coordinates in ix, iy
*/
float a = square(x2 - x1) + square(y2 - y1);
float b = 2* ((x2 - x1)*(x1 - x3)+ (y2 - y1)*(y1 - y3));
float c = square(x3) + square(y3) + square(x1) + square(y1) - 2*(x3*x1 + y3*y1) - square(r);
// x,y are calculated points of intersection
// these put it 'past' the unit
/*
float mu = (float) ((-b + Math.sqrt( square(b) - 4*a*c )) / (2*a));
float x = x1 + mu*(x2-x1);
float y = y1 + mu*(y2-y1);
*/
// these put it 'before' the unit
//
float mu = (float) ((-b - Math.sqrt(square(b) - 4*a*c )) / (2*a));
if(Float.isNaN(mu))
{
return new Point2D.Float(x1, y1);
}
Point2D.Float pt = new Point2D.Float();
pt.x = x1 + mu*(x2-x1);
pt.y = y1 + mu*(y2-y1);
// debug code
/*
if(Float.isNaN(pt.x) || Float.isNaN(pt.y))
{
System.out.println("NAN! in getLineCircleIntersection()");
System.out.println("args: "+x1+","+y1+","+x2+","+y2+","+x3+","+y3+","+r);
System.out.println("a,b,c: "+a+","+b+","+c);
System.out.println("mu: "+mu);
System.out.println("pt: "+pt);
}
*/
assert(!Float.isNaN(pt.x));
assert(!Float.isNaN(pt.y));
//System.out.println("args: L: "+x1+","+y1+","+x2+","+y2+", C: "+x3+","+y3+","+r);
//System.out.println(" result: "+pt);
return pt;
}// getLineCircleIntersection()
/**
* Respect a Radius II
* <p>
* Computes a point-line intersection and returns the result as a Point2D.Float
* object. This returns the second intersected point, or if there is only a single
* point, the second point the line would hit if it was extended to intersect
* the circle again.
* <p>
* <b>NOTE:</b> This assumes that the point and line intersect.
* <p>
* If x1,y1 == x2,y2 (this can happen if order verification is lenient) or other
* condition occurs where the internal value mu is NaN, we will return the
* point (x1,y1).
*
*/
public static Point2D.Float getLineCircleIntersectOuter(float x1, float y1, float x2, float y2, float x3, float y3, float r)
{
// how to respect radius:
/*
via circle-line intersection:
line: (x1,y1) to (x2,y2)
circle: radius (r), center (x3,y3)
intersection coordinates in ix, iy
*/
float a = square(x2 - x1) + square(y2 - y1);
float b = 2* ((x2 - x1)*(x1 - x3)+ (y2 - y1)*(y1 - y3));
float c = square(x3) + square(y3) + square(x1) + square(y1) - 2*(x3*x1 + y3*y1) - square(r);
// x,y are calculated points of intersection
// these put it 'past' the unit
float mu = (float) ((-b + Math.sqrt( square(b) - 4*a*c )) / (2*a));
if(Float.isNaN(mu))
{
return new Point2D.Float(x1, y1);
}
Point2D.Float pt = new Point2D.Float();
pt.x = x1 + mu*(x2-x1);
pt.y = y1 + mu*(y2-y1);
// these put it 'before' the unit
/*
float mu = (float) ((-b - Math.sqrt(square(b) - 4*a*c )) / (2*a));
Point2D.Float pt = new Point2D.Float();
pt.x = x1 + mu*(x2-x1);
pt.y = y1 + mu*(y2-y1);
*/
// debug code
/*
if(Float.isNaN(pt.x) || Float.isNaN(pt.y))
{
System.out.println("NAN! in getLineCircleIntersectOuter()");
System.out.println("args: "+x1+","+y1+","+x2+","+y2+","+x3+","+y3+","+r);
System.out.println("a,b,c: "+a+","+b+","+c);
System.out.println("mu: "+mu);
System.out.println("pt: "+pt);
}
*/
assert(!Float.isNaN(pt.x));
assert(!Float.isNaN(pt.y));
return pt;
}// getLineCircleIntersectOuter()
/**
* Applies the appropriate Stroke color and Filter (if any) to an element.
* <p>
* mmdOrderElementName = e.g., MapMetadata.EL_HOLD
*/
public static void makeStyled(SVGElement element, MapMetadata mmd, String mmdOrderElementName, Power power)
{
element.setAttributeNS(null, CSSConstants.CSS_STROKE_PROPERTY, mmd.getPowerColor(power));
String filter = mmd.getOrderParamString(mmdOrderElementName, MapMetadata.ATT_FILTERID);
if(filter.length() > 0)
{
StringBuffer sb = new StringBuffer(filter.length() + 6);
sb.append("url(#");
sb.append(filter);
sb.append(')');
element.setAttributeNS(null, SVGConstants.SVG_FILTER_ATTRIBUTE, sb.toString());
}
}// makeStyled()
/**
* Applies the appropriate Stroke color and Filter (if any) to an array of elements
* <p>
* mmdOrderElementName = e.g., MapMetadata.EL_HOLD
*/
public static void makeStyled(SVGElement[] elements, MapMetadata mmd, String mmdOrderElementName, Power power)
{
String filter = mmd.getOrderParamString(mmdOrderElementName, MapMetadata.ATT_FILTERID);
if(filter.length() > 0)
{
StringBuffer sb = new StringBuffer(filter.length() + 6);
sb.append("url(#");
sb.append(filter);
sb.append(')');
filter = sb.toString();
}
else
{
filter = null;
}
String powerColor = mmd.getPowerColor(power);
for(int i=0; i<elements.length; i++)
{
elements[i].setAttributeNS(null, CSSConstants.CSS_STROKE_PROPERTY, powerColor);
if(filter != null)
{
elements[i].setAttributeNS(null, SVGConstants.SVG_FILTER_ATTRIBUTE, filter);
}
}
}// makeStyled()
/**
* Sets the higlight of an element. Assumes that hilight is not set to 'none'
*/
public static void makeHilight(SVGElement element, MapMetadata mmd, String mmdOrderElementName)
{
String cssStyle = mmd.getOrderParamString(mmdOrderElementName, MapMetadata.ATT_HILIGHT_CLASS);
element.setAttributeNS(null, SVGConstants.SVG_CLASS_ATTRIBUTE, cssStyle);
}// makeHilight()
/**
* Sets the higlight of an array of elements. Assumes that hilight is not set to 'none'
*/
public static void makeHilight(SVGElement[] elements, MapMetadata mmd, String mmdOrderElementName)
{
String cssStyle = mmd.getOrderParamString(mmdOrderElementName, MapMetadata.ATT_HILIGHT_CLASS);
for(int i=0; i<elements.length; i++)
{
elements[i].setAttributeNS(null, SVGConstants.SVG_CLASS_ATTRIBUTE, cssStyle);
}
}// makeHilight()
/**
* Adds the given end-Marker to an element (usually a Line)
*
*/
public static void addMarker(SVGElement element, MapMetadata mmd, String mmdOrderElementName)
{
element.setAttributeNS(null, CSSConstants.CSS_MARKER_END_PROPERTY,
"url(#"+mmd.getOrderParamString(mmdOrderElementName, MapMetadata.ATT_MARKERID)+')');
}// addMarker()
/**
* Given a TurnState, determines if any order exists that matches the
* given Move order. Returns null if no matching Move order found.
*
*/
public static Move findMatchingMove(MapInfo mapInfo, Province src, Province dest)
{
Power[] powers = mapInfo.getDisplayablePowers();
for(int i=0; i<powers.length; i++)
{
List orders = mapInfo.getTurnState().getOrders(powers[i]);
Iterator iter = orders.iterator();
while(iter.hasNext())
{
Orderable o = (Orderable) iter.next();
if(o instanceof Move)
{
Move mv = (Move) o;
if( mv.getSource().isProvinceEqual(src)
&& mv.getDest().isProvinceEqual(dest) )
{
return mv;
}
}
}
}
return null;
}// findMatchingMove()
/**
* Given a TurnState, determines if any order exists that matches the
* given Hold order. Returns null if no matching Hold order found.
*
*/
public static Hold findMatchingHold(MapInfo mapInfo, Province src)
{
Power[] powers = mapInfo.getDisplayablePowers();
for(int i=0; i<powers.length; i++)
{
List orders = mapInfo.getTurnState().getOrders(powers[i]);
Iterator iter = orders.iterator();
while(iter.hasNext())
{
Orderable o = (Orderable) iter.next();
if(o instanceof Hold && o.getSource().isProvinceEqual(src))
{
return (Hold) o;
}
}
}
return null;
}// findMatchingHold()
/**
* Given a TurnState, determines if the number (if any) of .
* Support orders that match the given Move or Hold order.
* (use src == dest for Hold orders)
* <p>
* Note that only the displayable powers are used to check
* the support.
*/
public static int getMatchingSupportCount(MapInfo mapInfo, Province supSrc, Province supDest)
{
int count = 0;
Power[] powers = mapInfo.getDisplayablePowers();
for(int i=0; i<powers.length; i++)
{
List orders = mapInfo.getTurnState().getOrders(powers[i]);
Iterator iter = orders.iterator();
while(iter.hasNext())
{
Orderable o = (Orderable) iter.next();
if(o instanceof Support)
{
Support sup = (Support) o;
if( sup.getSupportedSrc().isProvinceEqual(supSrc)
&& sup.getSupportedDest().isProvinceEqual(supDest) )
{
count++;
}
}
}
}
return count;
}// findMatchingSupports()
/**
* Checks that support width is in-bounds. Note that negative widths are possible (see the
* base move modifier; e.g., Loeb9). If the support is out-of-bounds (max), the largest
* width is returned.
* <p>
* All negative widths are treated alike; DPB_LINE_WIDTH times the value of the
* smallest width in the line-width list (index 0).
*/
public static float getLineWidth(MapInfo mapInfo, String mmdElementName, String mmdElementType, int support)
{
int idx = support;
if(support < 0)
{
idx = 0;
}
final float[] widths = mapInfo.getMapMetadata().getOrderParamFloatArray(mmdElementName, mmdElementType);
if(support >= widths.length)
{
return widths[widths.length-1];
}
return (support >= 0) ? widths[idx] : (widths[idx] * DPB_LINE_WIDTH);
}// getLineWidth()
/**
* Returns the midpoint of a line.
*
*/
public static Point2D.Float getLineMidpoint(float x1, float y1, float x2, float y2)
{
Point2D.Float p2d = new Point2D.Float();
p2d.x = (x1 + x2) / 2.0f;
p2d.y = (y1 + y2) / 2.0f;
return p2d;
}// getLineMidpoint()
/**
* Create a <use> element with a SYMBOL_FAILEDORDER at the
* given coordinates, sized appropriately.
*/
public static SVGUseElement createFailedOrderSymbol(MapInfo mapInfo, float x, float y)
{
MapMetadata.SymbolSize symbolSize =
mapInfo.getMapMetadata().getSymbolSize(DefaultMapRenderer2.SYMBOL_FAILEDORDER);
return SVGUtils.createUseElement(
mapInfo.getDocument(),
"#"+DefaultMapRenderer2.SYMBOL_FAILEDORDER,
null, // no id
null, // no special style
x,
y,
symbolSize);
}// createFailedOrderSymbol()
/**
* Determine if the passed SVGGElement has any children. If it does,
* delete them.
*/
public static void deleteChildren(SVGGElement g)
{
if(g == null)
{
throw new IllegalArgumentException();
}
Node child = g.getFirstChild();
while(child != null)
{
g.removeChild( child );
child = g.getFirstChild();
}
}// deleteChildren()
/**
* Returns true if the given power is a member of the displayble
* powers group, <b>or</b> the TurnState is resolved (and orders
* for all powers can be shown)
*/
public static boolean isDisplayable(final Power power, final MapInfo mapInfo)
{
if(mapInfo.getTurnState().isResolved())
{
return true;
}
final Power[] displayedPowers = mapInfo.getDisplayablePowers();
for(int i=0; i<displayedPowers.length; i++)
{
if(displayedPowers[i].equals(power))
{
return true;
}
}
return false;
}// isDisplayable()
/**
* Removes a child element from the parent. If child is present,
* and succesfully removed, returns <code>true</code>. Otherwise,
* this method returns <code>false</code>. Null arguments are not
* permissable. No exceptions will be returned.
*/
public static boolean removeChild(final Element parent, final Element child)
{
Node node = parent.getFirstChild();
while(node != null)
{
if(node == child)
{
try
{
parent.removeChild(node);
return true;
}
catch(DOMException e)
{
return false;
}
}
node = node.getNextSibling();
}
return false;
}// removeChild()
/** Squares the given value */
private static float square(float v)
{
return (v*v);
}// square()
/**
* Formats a Floating-Point value into a String,
* using the jDip default precision.
*/
public static String floatToString(float v)
{
return SVGUtils.floatToString(v);
}// toString()
/**
* Formats a Floating-Point value into a StringBuffer,
* using the jDip default precision.
*/
public static void appendFloat(StringBuffer sb, float v)
{
SVGUtils.appendFloat(sb, v);
}// appendFloat()
}// class GUIOrderUtils