//
// @(#)GUISupport.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.gui.map.DefaultMapRenderer2;
import dip.gui.map.MapMetadata;
import dip.gui.order.GUIOrder.MapInfo;
import dip.misc.Utils;
import dip.order.Move;
import dip.order.Orderable;
import dip.order.Support;
import dip.order.ValidationOptions;
import dip.process.Adjustment.AdjustmentInfoMap;
import dip.process.RetreatChecker;
import dip.world.Coast;
import dip.world.Location;
import dip.world.Path;
import dip.world.Position;
import dip.world.Power;
import dip.world.Province;
import dip.world.RuleOptions;
import dip.world.TurnState;
import dip.world.Unit;
import java.awt.geom.Point2D;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.util.CSSConstants;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.svg.SVGCircleElement;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGLineElement;
import org.w3c.dom.svg.SVGPathElement;
import org.w3c.dom.svg.SVGPolygonElement;
import org.w3c.dom.svg.*;
import org.w3c.dom.*;
/**
*
* GUIOrder subclass of Support order.
* <p>
* Narrowing-order input via the GUI is not yet supported.
*
*/
public class GUISupport extends Support implements GUIOrder
{
// i18n keys
private final static String CLICK_TO_SUPPORT_UNIT = "GUISupport.click_to_sup";
private final static String NO_UNIT_TO_SUPPORT = "GUISupport.no_unit_to_sup";
private final static String CANNOT_SUPPORT_SELF = "GUISupport.no_self_sup";
private final static String CLICK_TO_SUPPORT_FROM = "GUISupport.click_to_sup_from";
private final static String CLICK_TO_SUPPORT_HOLD = "GUISupport.click_to_sup_hold";
private final static String SUP_DEST_NOT_ADJACENT = "GUISupport.sup_dest_not_adj";
private final static String SUPPORTING_THIS_UNIT = "GUISupport.sup_this_unit";
private final static String CLICK_TO_SUPPORT_MOVE = "GUISupport.click_to_sup_move";
private final static String CLICK_TO_SUPPORT_CONVOYED_MOVE = "GUISupport.click_to_sup_conv_move";
private final static String CANNOT_SUPPORT_MOVE_NONADJACENT = "GUISupport.move_nonadj";
private final static String CANNOT_SUPPORT_MOVE_GENERAL = "GUISupport.move_bad";
private final static String CANNOT_SUPPORT_ACROSS_DPB = "GUISupport.over_dpb";
// instance variables
private transient static final int REQ_LOC = 3;
private transient int currentLocNum = 0;
private transient boolean dependentFound = false; // true associated Move or Support order found
private transient Point2D.Float failPt = null;
private transient SVGGElement group = null;
/** Creates a GUISupport */
protected GUISupport()
{
super();
}// GUISupport()
/** Creates a GUISupport */
protected GUISupport(Power power, Location src, Unit.Type srcUnitType,
Location supSrc, Power supPower, Unit.Type supUnitType)
{
super(power, src, srcUnitType, supSrc, supPower, supUnitType);
}// GUISupport()
/** Creates a GUISupport */
protected GUISupport(Power power, Location src, Unit.Type srcUnitType,
Location supSrc, Power supPower, Unit.Type supUnitType, Location supDest)
{
super(power, src, srcUnitType, supSrc, supPower, supUnitType, supDest);
}// GUISupport()
/**
* This only accepts Support orders.
* All others will throw an IllegalArgumentException.
*/
public void deriveFrom(Orderable order)
{
if( !(order instanceof Support) )
{
throw new IllegalArgumentException();
}
Support support = (Support) order;
power = support.getPower();
src = support.getSource();
srcUnitType = support.getSourceUnitType();
supSrc = support.getSupportedSrc();
supDest = support.getSupportedDest();
supPower = support.getSupportedPower();
supUnitType = support.getSupportedUnitType();
narrowingOrder = support.getNarrowingOrder();
// set completed
currentLocNum = REQ_LOC;
}// deriveFrom()
public boolean testLocation(StateInfo stateInfo, Location location, StringBuffer sb)
{
sb.setLength(0);
if(isComplete())
{
sb.append( Utils.getLocalString(GUIOrder.COMPLETE, getFullName()) );
return false;
}
Position position = stateInfo.getPosition();
Province province = location.getProvince();
if(currentLocNum == 0)
{
// set Support origin (supporting unit)
// we require a unit present. We will check unit ownership too, if appropriate
Unit unit = position.getUnit(province);
if(unit != null)
{
if( !stateInfo.canIssueOrder(unit.getPower()) )
{
sb.append( Utils.getLocalString(GUIOrder.NOT_OWNER, unit.getPower()) );
return false;
}
// check borders
if( !GUIOrderUtils.checkBorder(this, new Location(province, unit.getCoast()), unit.getType(), stateInfo.getPhase(), sb) )
{
return false;
}
sb.append( Utils.getLocalString(GUIOrder.CLICK_TO_ISSUE, getFullName()) );
return true;
}
// no unit in province
sb.append( Utils.getLocalString(GUIOrder.NO_UNIT, getFullName()) );
return false;
}
else if(currentLocNum == 1)
{
// set Support source (unit receiving support)
// - If we are not validating, any location with a unit is acceptable (even source)
// - If we are validating,
//
if(stateInfo.getValidationOptions().getOption(ValidationOptions.KEY_GLOBAL_PARSING).equals(ValidationOptions.VALUE_GLOBAL_PARSING_LOOSE))
{
// lenient parsing enabled; we'll take anything with a unit!
if(position.hasUnit(province))
{
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_UNIT) );
return true;
}
// no unit in province
sb.append( Utils.getLocalString(NO_UNIT_TO_SUPPORT) );
return false;
}
// strict parsing is enabled. We are more selective.
// This location must contain a unit, and not be the same as the unit originating support.
//
if(province == src.getProvince())
{
sb.append( Utils.getLocalString(CANNOT_SUPPORT_SELF) );
return false;
}
else if(position.hasUnit(province))
{
// check borders
Unit supUnit = position.getUnit(province);
if( !GUIOrderUtils.checkBorder(this, new Location(province, supUnit.getCoast()), supUnit.getType(), stateInfo.getPhase(), sb) )
{
return false;
}
// check base movement modifier (DPB)
if(province.getBaseMoveModifier(getSource()) < 0)
{
sb.append( Utils.getLocalString(CANNOT_SUPPORT_ACROSS_DPB) );
return false;
}
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_UNIT) );
return true;
}
// no unit in province
sb.append( Utils.getLocalString(NO_UNIT_TO_SUPPORT) );
return false;
}
else if(currentLocNum == 2)
{
// set Supporting-into Location
// - If we are not validating, any destination is acceptable (even source)
// - If we are validating, we check that the Support is adjacent
//
if(stateInfo.getValidationOptions().getOption(ValidationOptions.KEY_GLOBAL_PARSING).equals(ValidationOptions.VALUE_GLOBAL_PARSING_LOOSE))
{
// lenient parsing enabled; we'll take anything!
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_FROM, province.getFullName()) );
return true;
}
// strict parsing is enabled. We are more selective.
// This location must be adjacent to the origin
//
if(src.isAdjacent(province))
{
// special case: supportSrc == supportDest; we are supporting a hold.
if(supSrc.getProvince() == province)
{
// NOTE: no border check required here.
// we are supporting a unit holding in place.
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_HOLD) );
return true;
}
// for supported moves, we must do a Border check
if( !GUIOrderUtils.checkBorder(this, location, supUnitType, stateInfo.getPhase(), sb) )
{
return false;
}
// all other cases (supported move)
return checkMove(position, supSrc, location, sb);
}
// supDest is not adjacent to src province
sb.append( Utils.getLocalString(SUP_DEST_NOT_ADJACENT, src.toLongString()) );
return false;
}
else
{
// should not occur.
throw new IllegalStateException();
}
// NO return here: thus we must appropriately exit within an if/else block above.
}// testLocation()
public boolean clearLocations()
{
if(isComplete())
{
return false;
}
currentLocNum = 0;
power = null;
src = null;
srcUnitType = null;
supSrc = null;
supDest = null;
supPower = null;
supUnitType = null;
return true;
}// clearLocations()
public boolean setLocation(StateInfo stateInfo, Location location, StringBuffer sb)
{
if(isComplete())
{
return false;
}
if(testLocation(stateInfo, location, sb))
{
if(currentLocNum == 0)
{
Unit unit = stateInfo.getPosition().getUnit(location.getProvince());
src = new Location(location.getProvince(), unit.getCoast());
power = unit.getPower();
srcUnitType = unit.getType();
currentLocNum++;
return true;
}
else if(currentLocNum == 1)
{
Unit unit = stateInfo.getPosition().getUnit(location.getProvince());
supSrc = new Location(location.getProvince(), unit.getCoast());
supPower = unit.getPower();
supUnitType = unit.getType();
sb.setLength(0);
sb.append( Utils.getLocalString(SUPPORTING_THIS_UNIT) );
currentLocNum++;
return true;
}
else if(currentLocNum == 2)
{
if(supSrc.getProvince() == location.getProvince())
{
supDest = null; // this means we are supporting a Hold
}
else
{
supDest = new Location(location.getProvince(), location.getCoast());
}
sb.setLength(0);
sb.append( Utils.getLocalString(GUIOrder.COMPLETE, getFullName()) );
currentLocNum++;
return true;
}
}
return false;
}// setLocation()
public boolean isComplete()
{
assert (currentLocNum <= getNumRequiredLocations());
return (currentLocNum == getNumRequiredLocations());
}// isComplete()
public int getNumRequiredLocations() { return REQ_LOC; }
public int getCurrentLocationNum() { return currentLocNum; }
/** Always throws an IllegalArgumentException */
public void setParam(Parameter param, Object value) { throw new IllegalArgumentException(); }
/** Always throws an IllegalArgumentException */
public Object getParam(Parameter param) { throw new IllegalArgumentException(); }
public void removeFromDOM(MapInfo mapInfo)
{
if(group != null)
{
SVGGElement powerGroup = mapInfo.getPowerSVGGElement(power, LAYER_LOWEST);
GUIOrderUtils.removeChild(powerGroup, group);
group = null;
}
}// removeFromDOM()
/**
* For supported holds: draws a dashed line to a dashed octagon
* <p>
* For supported moves: draws a dashed line to the supported unit,
* then draws a dashed circle around the unit, then
* draws a dashed line with arrow representing the move.
*
*/
public void updateDOM(MapInfo mapInfo)
{
// if we are not displayable, we exit, after remove the order (if
// it was created)
if( !GUIOrderUtils.isDisplayable(power, mapInfo) )
{
removeFromDOM(mapInfo);
return;
}
// determine if any change has occured. If no change has occured,
// we will not change the DOM.
//
// check dependent order status.
boolean found = false;
if(isSupportingHold())
{
// Support a hold
found = (GUIOrderUtils.findMatchingHold(mapInfo,
supSrc.getProvince()) != null);
}
else
{
// Support a move
found = (GUIOrderUtils.findMatchingMove(mapInfo,
supSrc.getProvince(), supDest.getProvince()) != null);
}
if(group != null && dependentFound == found)
{
return; // no change, and we are not newly created
}
// we are only at this point if a change has occured.
//
dependentFound = found;
// if we've not yet been created, we will create; if we've
// already been created, we must remove the existing elements
// in our group
if(group == null)
{
// create group
group = (SVGGElement) mapInfo.getDocument().createElementNS(
SVGDOMImplementation.SVG_NAMESPACE_URI, SVGConstants.SVG_G_TAG);
group.setId("order_" + this.src.toString());
mapInfo.getPowerSVGGElement(power, LAYER_LOWEST).appendChild(group);
}
else
{
// remove group children
GUIOrderUtils.deleteChildren(group);
}
// now, render the order
//
// draw after determining if we are supporting a hold or not
if(isSupportingHold())
{
updateDOMHold(mapInfo);
}
else
{
updateDOMMove(mapInfo);
}
// draw 'failed' marker, if appropriate.
if(!mapInfo.getTurnState().isOrderSuccessful(this))
{
SVGElement useElement = GUIOrderUtils.createFailedOrderSymbol(mapInfo, failPt.x, failPt.y);
group.appendChild(useElement);
}
}// updateDOM()
/** Draw Supported Hold order */
private void updateDOMHold(MapInfo mapInfo)
{
SVGElement[] elements = null;
// create hilight line
String cssStyle = mapInfo.getMapMetadata().getOrderParamString(MapMetadata.EL_SUPPORT, MapMetadata.ATT_HILIGHT_CLASS);
if(!cssStyle.equalsIgnoreCase("none"))
{
float offset = mapInfo.getMapMetadata().getOrderParamFloat(MapMetadata.EL_SUPPORT, MapMetadata.ATT_HILIGHT_OFFSET);
elements = drawSupportedHold(mapInfo, offset);
GUIOrderUtils.makeHilight(elements, mapInfo.getMapMetadata(), MapMetadata.EL_SUPPORT);
for(int i=0; i<elements.length; i++)
{
group.appendChild(elements[i]);
}
}
// create real line
elements = drawSupportedHold(mapInfo, 0);
GUIOrderUtils.makeStyled(elements, mapInfo.getMapMetadata(), MapMetadata.EL_SUPPORT, power);
for(int i=0; i<elements.length; i++)
{
group.appendChild(elements[i]);
}
}// updateDOMHold()
/** Draw Supported Move order */
private void updateDOMMove(MapInfo mapInfo)
{
SVGElement[] elements = null;
// create hilight line
String cssStyle = mapInfo.getMapMetadata().getOrderParamString(MapMetadata.EL_SUPPORT, MapMetadata.ATT_HILIGHT_CLASS);
if(!cssStyle.equalsIgnoreCase("none"))
{
float offset = mapInfo.getMapMetadata().getOrderParamFloat(MapMetadata.EL_SUPPORT, MapMetadata.ATT_HILIGHT_OFFSET);
elements = drawSupportedMove(mapInfo, offset, false);
GUIOrderUtils.makeHilight(elements, mapInfo.getMapMetadata(), MapMetadata.EL_SUPPORT);
for(int i=0; i<elements.length; i++)
{
group.appendChild(elements[i]);
}
}
// create real line
elements = drawSupportedMove(mapInfo, 0, true);
GUIOrderUtils.makeStyled(elements, mapInfo.getMapMetadata(), MapMetadata.EL_SUPPORT, power);
for(int i=0; i<elements.length; i++)
{
group.appendChild(elements[i]);
}
}// updateDOMMove()
/**
* Note: we don't draw the 3rd part (supSrc->supDest) if moveFound == false.
*
*
*/
private SVGElement[] drawSupportedMove(MapInfo mapInfo, float offset, boolean addMarker)
{
// setup
//SVGElement[] elements = new SVGElement[ ((dependentFound) ? 1 : 3) ];
SVGElement[] elements = new SVGElement[1];
Position position = mapInfo.getTurnState().getPosition();
MapMetadata mmd = mapInfo.getMapMetadata();
Point2D.Float ptSrc = mmd.getUnitPt(src.getProvince(), src.getCoast());
Point2D.Float ptSupSrc = mmd.getUnitPt(supSrc.getProvince(), supSrc.getCoast());
Point2D.Float ptSupDest = mmd.getUnitPt(supDest.getProvince(), supDest.getCoast());
// adjust for offset
ptSrc.x += offset;
ptSrc.y += offset;
ptSupSrc.x += offset;
ptSupSrc.y += offset;
ptSupDest.x += offset;
ptSupDest.y += offset;
// destination. If no unit, use the size of an army unit radius divided
// by 2 (as we do in GUIMove)
//
Point2D.Float newSupDest = null;
if(position.hasUnit(supDest.getProvince()))
{
// since we're supporting a Move, we should use the Move radius
Unit.Type destUnitType = position.getUnit(supDest.getProvince()).getType();
float moveRadius = mmd.getOrderRadius(MapMetadata.EL_MOVE, mapInfo.getSymbolName(destUnitType));
newSupDest = GUIOrderUtils.getLineCircleIntersection(ptSupSrc.x, ptSupSrc.y, ptSupDest.x, ptSupDest.y, ptSupDest.x, ptSupDest.y, moveRadius);
}
else
{
float moveRadius = (mmd.getOrderRadius(MapMetadata.EL_MOVE, mapInfo.getSymbolName(Unit.Type.ARMY)) / 2);
newSupDest = GUIOrderUtils.getLineCircleIntersection(ptSupSrc.x, ptSupSrc.y, ptSupDest.x, ptSupDest.y, ptSupDest.x, ptSupDest.y, moveRadius);
}
// calculate failed point marker
// this is sort of a hack; may not really coincide with bezier curve!!
// seems to work fairly well, though. keep for now.
failPt = GUIOrderUtils.getLineMidpoint(ptSrc.x, ptSrc.y, ptSupSrc.x, ptSupSrc.y);
// new test code -- bezier
elements[0] = (SVGPathElement)
mapInfo.getDocument().createElementNS(
SVGDOMImplementation.SVG_NAMESPACE_URI,
SVGConstants.SVG_PATH_TAG);
StringBuffer sb = new StringBuffer();
sb.append("M ");
GUIOrderUtils.appendFloat(sb, ptSrc.x); // unit start
sb.append(',');
GUIOrderUtils.appendFloat(sb, ptSrc.y);
sb.append(" C ");
GUIOrderUtils.appendFloat(sb, ptSupSrc.x); // supporting unit
sb.append(',');
GUIOrderUtils.appendFloat(sb, ptSupSrc.y);
sb.append(' ');
GUIOrderUtils.appendFloat(sb, ptSupSrc.x); // supporting unit
sb.append(',');
GUIOrderUtils.appendFloat(sb, ptSupSrc.y);
sb.append(' ');
GUIOrderUtils.appendFloat(sb, newSupDest.x); // destination
sb.append(',');
GUIOrderUtils.appendFloat(sb, newSupDest.y);
sb.append(' ');
elements[0].setAttributeNS(null, SVGConstants.SVG_D_ATTRIBUTE, sb.toString());
elements[0].setAttributeNS(null, SVGConstants.SVG_CLASS_ATTRIBUTE,
mmd.getOrderParamString(MapMetadata.EL_SUPPORT, MapMetadata.ATT_STROKESTYLE));
if(addMarker || offset != 0.0f)
{
GUIOrderUtils.addMarker(elements[0], mmd, MapMetadata.EL_SUPPORT);
}
// return
return elements;
}// drawSupportedMove()
private SVGElement[] drawSupportedHold(MapInfo mapInfo, float offset)
{
// setup
SVGElement[] elements = new SVGElement[ ((dependentFound) ? 1 : 2) ];
MapMetadata mmd = mapInfo.getMapMetadata();
Point2D.Float ptSrc = mmd.getUnitPt(src.getProvince(), src.getCoast());
Point2D.Float ptSupSrc = mmd.getUnitPt(supSrc.getProvince(), supSrc.getCoast());
// supUnitType shouldn't be null here...
float radius = mmd.getOrderRadius(MapMetadata.EL_SUPPORT, mapInfo.getSymbolName(supUnitType));
// adjust for offset
ptSrc.x += offset;
ptSrc.y += offset;
ptSupSrc.x += offset;
ptSupSrc.y += offset;
// draw line to the octagon
Point2D.Float newPtTo = GUIOrderUtils.getLineCircleIntersection(ptSrc.x, ptSrc.y,
ptSupSrc.x, ptSupSrc.y, ptSupSrc.x, ptSupSrc.y, radius);
elements[0] = (SVGLineElement)
mapInfo.getDocument().createElementNS(
SVGDOMImplementation.SVG_NAMESPACE_URI,
SVGConstants.SVG_LINE_TAG);
elements[0].setAttributeNS(null, SVGConstants.SVG_X1_ATTRIBUTE, GUIOrderUtils.floatToString(ptSrc.x));
elements[0].setAttributeNS(null, SVGConstants.SVG_Y1_ATTRIBUTE, GUIOrderUtils.floatToString(ptSrc.y));
elements[0].setAttributeNS(null, SVGConstants.SVG_X2_ATTRIBUTE, GUIOrderUtils.floatToString(newPtTo.x));
elements[0].setAttributeNS(null, SVGConstants.SVG_Y2_ATTRIBUTE, GUIOrderUtils.floatToString(newPtTo.y));
elements[0].setAttributeNS(null, SVGConstants.SVG_CLASS_ATTRIBUTE,
mmd.getOrderParamString(MapMetadata.EL_SUPPORT, MapMetadata.ATT_STROKESTYLE));
// calculate (but don't yet use) failPt
failPt = GUIOrderUtils.getLineMidpoint(ptSrc.x, ptSrc.y, newPtTo.x, newPtTo.y);
// draw octagon
if(!dependentFound)
{
Point2D.Float[] pts = GUIOrderUtils.makeOctagon(ptSupSrc, radius);
StringBuffer sb = new StringBuffer(160);
for(int i=0; i<pts.length; i++)
{
GUIOrderUtils.appendFloat(sb, pts[i].x);
sb.append(',');
GUIOrderUtils.appendFloat(sb, pts[i].y);
sb.append(' ');
}
elements[1] = (SVGPolygonElement)
mapInfo.getDocument().createElementNS(
SVGDOMImplementation.SVG_NAMESPACE_URI,
SVGConstants.SVG_POLYGON_TAG);
elements[1].setAttributeNS(null, SVGConstants.SVG_POINTS_ATTRIBUTE, sb.toString());
elements[1].setAttributeNS(null, SVGConstants.SVG_CLASS_ATTRIBUTE, mmd.getOrderParamString(MapMetadata.EL_SUPPORT, MapMetadata.ATT_STROKESTYLE));
}
// return
return elements;
}// drawSupportedHold()
/** We are dependent upon other orders to determine how we render this order. */
public boolean isDependent() { return true; }
/** Check a Move between two locations, for adjacency (even by theoretical convoy route). */
private boolean checkMove(Position position, Location from, Location to, StringBuffer sb)
{
if(from.isAdjacent(to.getProvince()))
{
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_MOVE) );
return true;
}
else if(from.getProvince().isCoastal() && supUnitType == Unit.Type.ARMY)
{
// NOTE: assume destination coast is Coast.NONE
Path path = new Path(position);
if(path.isPossibleConvoyRoute(from, new Location(to.getProvince(), Coast.NONE)))
{
sb.append( Utils.getLocalString(CLICK_TO_SUPPORT_CONVOYED_MOVE) );
return true;
}
else
{
sb.append( Utils.getLocalString(CANNOT_SUPPORT_MOVE_NONADJACENT, from.getProvince().getFullName()) );
return false;
}
}
sb.append(Utils.getLocalString(CANNOT_SUPPORT_MOVE_GENERAL, from.getProvince().getFullName()) );
return false;
}// checkMove()
public SVGElement orderSVG(MapInfo mapInfo){
updateDOM(mapInfo);
return group;
}
}// class GUISupport