//
// @(#)MapRenderer2.java 5/2003
//
// Copyright 2003 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.map;
import dip.gui.map.RenderCommandFactory.RenderCommand;
import dip.gui.ClientFrame;
import dip.gui.AbstractCFPListener;
import dip.gui.order.GUIOrder;
import dip.order.Orderable;
import dip.misc.Log;
import dip.world.Province;
import dip.world.TurnState;
import dip.world.Location;
import dip.world.Power;
import dip.world.Unit;
import java.util.LinkedList;
import java.util.Iterator;
import org.w3c.dom.svg.SVGDocument;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.util.RunnableQueue;
import org.apache.batik.bridge.UpdateManagerListener;
import org.apache.batik.bridge.UpdateManagerEvent;
/**
* Base class for the new MapRenderer.
* <p>
* Implementation notes: remember to <b>always</b> synchronize around the
* <code>renderQueue</code> object.
*
*/
public abstract class MapRenderer2
{
// constant key values for getRenderSetting()
//
/** Key for getSettings(): */
public static final String KEY_LABELS = "KEY_LABELS";
/** Key for getSettings(): */
public static final String KEY_SHOW_SUPPLY_CENTERS = "KEY_SHOW_SUPPLY_CENTERS";
/** Key for getSettings(): */
public static final String KEY_SHOW_UNITS = "KEY_SHOW_UNITS";
/** Key for getSettings(): */
public static final String KEY_SHOW_DISLODGED_UNITS = "KEY_SHOW_DISLODGED_UNITS";
/** Key for getSettings(): */
public static final String KEY_SHOW_UNORDERED = "KEY_SHOW_UNORDERED";
/** Key for getSettings(): */
public static final String KEY_SHOW_ORDERS_FOR_POWERS = "KEY_SHOW_ORDERS_FOR_POWERS";
/** Key for getSettings(): */
public static final String KEY_INFLUENCE_MODE = "KEY_INFLUENCE_MODE";
/** Key for getSettings(): */
public static final String KEY_SHOW_MAP = "KEY_SHOW_MAP";
// constant return values for getRenderSetting()
//
/** Value returned from getSettings(): */
public static final String VALUE_LABELS_NONE = "VALUE_LABELS_NONE";
/** Value returned from getSettings(): */
public static final String VALUE_LABELS_FULL = "VALUE_LABELS_FULL";
/** Value returned from getSettings(): */
public static final String VALUE_LABELS_BRIEF = "VALUE_LABELS_BRIEF";
// instance variables
//
private boolean isReady = false; // internal flag indicating if turnstate has been set
private LinkedList tempQueue = null;
protected final MapPanel mapPanel;
protected CFPropertyListener propListener = null;
protected final JSVGCanvas svgCanvas;
protected final SVGDocument doc;
/**
* Default Constructor
* JSVGCanvas and SVGDocument of MapPanel <b>must not be null</b>
*/
public MapRenderer2(MapPanel mp)
throws MapException
{
Log.printTimed(mp.startTime, "MR2 constructor start");
mapPanel = mp;
svgCanvas = mapPanel.getJSVGCanvas();
doc = svgCanvas.getSVGDocument();
propListener = new CFPropertyListener();
mapPanel.getClientFrame().addPropertyChangeListener(propListener);
tempQueue = new LinkedList();
Log.printTimed(mp.startTime, "MR2 constructor end");
}// MapRenderer()
/** Convenience method */
public final ClientFrame getClientFrame()
{
return mapPanel.getClientFrame();
}// getClientFrame()
/**
* Gets the Runnable Queue for the canvas.
* Return null if this cannot be done (e.g., we are exiting).
*/
public final RunnableQueue getRunnableQueue()
{
if(svgCanvas != null)
{
if(svgCanvas.getUpdateManager() != null)
{
return svgCanvas.getUpdateManager().getUpdateRunnableQueue();
}
}
Log.println("MR2::getRunnableQueue(): RQ null ... exiting?");
return null;
}// getRunnableQueue()
/**
* Get a setting (as defined by the KEY_ constants)
*/
public abstract Object getRenderSetting(final Object key);
/**
* Execute a RenderCommand. No commands are executed until the TurnState
* has been set.
*/
public synchronized void execRenderCommand(RenderCommand rc)
{
if(rc instanceof RenderCommandFactory.RCSetTurnstate)
{
// focus
mapPanel.requestFocusInWindow();
// dequeue pending events
isReady = true;
Log.println("MR2:execRenderCommand(): first RCSetTurnstate: ", rc);
clearAndExecute(rc, null);
// dequeue pending events, if any
if(!tempQueue.isEmpty())
{
Log.println("MR2::execRenderCommand(): removing pending events from queue. size: ",
String.valueOf(tempQueue.size()));
RunnableQueue rq = getRunnableQueue();
if(rq != null)
{
Iterator iter = tempQueue.iterator();
while(iter.hasNext())
{
rq.invokeLater( ((RenderCommand) iter.next()) );
}
tempQueue.clear();
}
}
}
else if(isReady)
{
// a RCSetTurnstate() has been issued. We can accept render events.
// if we have queued events, add them.
Log.println("MR2::execRenderCommand(): adding to RunnableQueue: ", rc);
RunnableQueue rq = getRunnableQueue();
if(rq != null)
{
rq.invokeLater(rc);
}
}
else
{
// we are not yet ready -- add the rendering events to a temporary queue.
Log.println("MR2::execRenderCommand(): adding to tempQueue: ", rc);
tempQueue.add(rc);
}
}// execRenderCommand()
/** Clean up any resources used by the MapRenderer. */
public void close()
{
isReady = false;
mapPanel.getClientFrame().removePropertyChangeListener(propListener);
}// close()
/** Get the RenderCommandFactory */
public abstract RenderCommandFactory getRenderCommandFactory();
/** Get the MapMetadata object */
public abstract MapMetadata getMapMetadata();
/** Get the Symbol Name for the given unit type */
public abstract String getSymbolName(Unit.Type unitType);
/** Get a location that corresponds to an ID */
public abstract Location getLocation(String id);
/** Called when an order has been deleted from the order list */
protected abstract void orderDeleted(GUIOrder order);
/** Called when an order has been added to the order list */
protected abstract void orderCreated(GUIOrder order);
/** Called when multiple orders have been deleted from the order list */
protected abstract void multipleOrdersDeleted(GUIOrder[] orders);
/** Called when multiple orders have been added from the order list */
protected abstract void multipleOrdersCreated(GUIOrder[] orders);
/** Called when the displayable powers have changed */
protected abstract void displayablePowersChanged(Power[] powers);
/**
* Prevents any enqueued RenderCommands from being executed.
* If a command is currently executing, it is not affected.
* Adds the given commands (or none, if null) to the queue.
*/
protected void clearAndExecute(RenderCommand rc1, RenderCommand rc2)
{
Log.println("MR2::clearAndExecute()");
RunnableQueue rq = getRunnableQueue();
if(rq != null)
{
synchronized(rq.getIteratorLock())
{
// kill our pending render events
Iterator iter = rq.iterator();
while(iter.hasNext())
{
Object obj = iter.next();
if(obj instanceof RenderCommand)
{
Log.println(" killing: ", obj);
((RenderCommand) obj).die();
}
}
// add our new render commands
// NOTE: not sure if this should be in synchronized block.
if(rc1 != null)
{
rq.invokeLater(rc1);
}
if(rc2 != null)
{
rq.invokeLater(rc2);
}
}
}
}// clearAndExecute()
/**
* Listener class for order updates and TurnState changes
*
*/
private class CFPropertyListener extends AbstractCFPListener
{
public void actionOrderCreated(Orderable order)
{
orderCreated((GUIOrder) order);
}
public void actionOrderDeleted(Orderable order)
{
orderDeleted((GUIOrder) order);
}
public void actionOrdersCreated(Orderable[] orders)
{
GUIOrder[] guiOrders = new GUIOrder[orders.length];
for(int i=0; i<guiOrders.length; i++)
{
guiOrders[i] = (GUIOrder) orders[i];
}
multipleOrdersCreated(guiOrders);
}
public void actionOrdersDeleted(Orderable[] orders)
{
GUIOrder[] guiOrders = new GUIOrder[orders.length];
for(int i=0; i<guiOrders.length; i++)
{
guiOrders[i] = (GUIOrder) orders[i];
}
multipleOrdersDeleted(guiOrders);
}
public void actionDisplayablePowersChanged(Power[] oldPowers, Power[] newPowers)
{
displayablePowersChanged(newPowers);
}
public void actionTurnstateChanged(TurnState ts)
{
// OPTIMIZATION:
// any pending queued events may be deleted, because
// we are changing the turnstate and doing a complete re-render.
//
RenderCommand rc1 =
getRenderCommandFactory().createRCSetTurnstate(
MapRenderer2.this, ts);
RenderCommand rc2 =
getRenderCommandFactory().createRCRenderAll(
MapRenderer2.this);
clearAndExecute(rc1, rc2);
}
}// inner class CFPropertyListener
/**
* Returns a Label-Level constant for the given label level.
* Does a case-insensitive compare. Instance equality is preserved.
* Returns the given default if parsing fails.
*/
public static String parseLabelValue(String in, String defaultValue)
{
if(defaultValue != VALUE_LABELS_NONE
&& defaultValue != VALUE_LABELS_FULL
&& defaultValue != VALUE_LABELS_BRIEF
&& defaultValue != null)
{
throw new IllegalArgumentException();
}
if(VALUE_LABELS_NONE.equalsIgnoreCase(in))
{
return VALUE_LABELS_NONE;
}
else if(VALUE_LABELS_BRIEF.equalsIgnoreCase(in))
{
return VALUE_LABELS_BRIEF;
}
else if(VALUE_LABELS_FULL.equalsIgnoreCase(in))
{
return VALUE_LABELS_FULL;
}
return defaultValue;
}// parseLabelValue()
}// abstract class MapRenderer2