//
// @(#)OrderDisplayPanel.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;
import dip.order.*;
import dip.world.*;
import dip.gui.undo.*;
import dip.gui.order.GUIOrder;
import dip.gui.swing.XJScrollPane;
import dip.gui.dialog.prefs.GeneralPreferencePanel;
import dip.misc.Utils;
import dip.process.Adjustment;
import dip.misc.Log;
import dip.order.result.Result;
import dip.order.result.OrderResult;
import cz.autel.dmi.*; // HIGLayout
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.undo.*;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.Font;
import java.awt.event.*;
import java.util.*;
import java.text.MessageFormat;
/**
* The OrderDisplayPanel: displayer of orders.
* <p>
* OrderDisplayPanel provides a wrapper for viewing orders within a TurnState object,
* as well as adding orders to a TurnState object.
* <p>
* Advantages of using OrderDisplayPanel to modify TurnState include:
* <ul>
* <li>Less code to add a single order to the TurnState</li>
* <li>Validates orders using current validation option settings</li>
* <li>Prevents duplicate orders from being added</li>
* <li>Prevents more than one order being issued from one province</li>
* <li>Allows optional Undo/Redo support for actions</li>
* <li>Checks for too many build/remove orders in Adjustment phase
* <li>Checks that added order is part of orderable powers group (array)</li>
* </ul>
* It is recommended that all orders are entered via the OrderDisplayPanel. Note
* that if orders are entered into the TurnState directly, approrpriate ClientFrame
* fire() methods must be called to inform the OrderDisplayPanel of updates. Also,
* care must be taken to avoid entering duplicate orders, and that only one order per
* unit (per province).
* <p>
* <b>Important Note:</b> The add/remove/removeAll methods will only operate on
* orderable power. Thus if an order is added and the power is not in the orderable
* power list, it will fail. This applies to removes as well. For removeAll, only
* orderable powers orders are removed.
*
*/
public class OrderDisplayPanel extends JPanel
{
// sorting constants
/** Sort Orders by Power */
public static final String SORT_POWER = "SORT_POWER";
/** Sort Orders by Province */
public static final String SORT_PROVINCE = "SORT_PROVINCE";
/** Sort Orders by Unit */
public static final String SORT_UNIT = "SORT_UNIT";
/** Sort Orders by Order type */
public static final String SORT_ORDER = "SORT_ORDER";
// color constants
private final static Color BG_DEFAULT = UIManager.getColor("List.background");
private final static Color BG_HILITE = new Color(230,238,240);
// i18n constants
private final static String ORD_ERR_AMBIGUOUS = "OP.order.err.ambiguous";
private final static String ORD_ERR_INVALID = "OP.order.err.invalid";
private final static String ORD_ERR_UNEDITABLE = "OP.order.err.uneditable";
private final static String ORD_ERR_NOT_ORDERABLE = "OP.order.err.notorderable";
private final static String LABEL_SORT = "OP.label.sort";
public final static String LABEL_SORT_POWER = "OP.sort.button.power";
public final static String LABEL_SORT_PROVINCE = "OP.sort.button.province";
public final static String LABEL_SORT_UNIT = "OP.sort.button.unit";
public final static String LABEL_SORT_ORDER = "OP.sort.button.order";
private final static String DLG_TOOMANY_TEXT_LOCATION = "OP.dlg.toomany.text.location";
// instance variables
protected AbstractCFPListener propListener = null;
protected ClientFrame clientFrame = null;
private ValidationOptions valOpts = null;
private OrderParser orderParser = null;
protected World world = null;
protected TurnState turnState = null;
private Power[] displayablePowers = null;
private Power[] orderablePowers = null;
private OrderListModel orderListModel = null;
private boolean isEditable = false;
private Adjustment.AdjustmentInfoMap adjMap = null; // non-null only in Adjustment phase
private UndoRedoManager undoManager = null;
// GUI component instance variables
private JList orderList;
protected JScrollPane orderListScrollPane;
/**
* Creates an OrderDisplayPanel
*
*
*
*/
public OrderDisplayPanel(ClientFrame clientFrame)
{
// init
this.clientFrame = clientFrame;
orderParser = OrderParser.getInstance();
// order list basic setup
orderListModel = new OrderListModel();
orderList = new JList(orderListModel);
orderList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
orderList.setDragEnabled(false);
orderList.setCellRenderer(new OrderListRenderer());
// set order list sort preference
setSorting(
GeneralPreferencePanel.getOrderSortMode(),
GeneralPreferencePanel.getOrderSortReverse()
);
orderListScrollPane = new XJScrollPane(orderList);
orderListScrollPane.getViewport().setBackground(orderList.getBackground()); // match table & scroll backgrounds
// setup property change listener
propListener = createPropertyListener();
clientFrame.addPropertyChangeListener(propListener);
// layout
makeLayout();
}// OrderDisplayPanel()
/**
* OrderDisplayPanel cleanup.
*
*/
public void close()
{
clientFrame.removePropertyChangeListener(propListener);
}// close()
/** Create the ODPPropertyListener used to receive events. */
protected AbstractCFPListener createPropertyListener()
{
return new ODPPropertyListener();
}// createPropertyListener()
/**
* Add an order to the order list, possibly with undo/redo support,
* and only succeeding if the order passes validation given the
* validation constraints. Duplicate orders are prevented.
* <p>
* This differs from addOrderRaw in that popup dialogs are displayed
* with an error message, instead of throwing OrderExceptions.
*
* @param order - <b>true</b> Orderable object
* @param undoable - <b>true</b> if this is an undoable action
* @return <b>true</b> if the order was accepted
*/
public synchronized boolean addOrder(Orderable order, boolean undoable)
{
try
{
addOrderRaw(order, undoable);
orderList.clearSelection();
return true;
}
catch(OrderWarning ow)
{
// order is still acceptable (and was accepted), but issue a warning message.
Utils.popupError(clientFrame, Utils.getLocalString(ORD_ERR_AMBIGUOUS), ow.getMessage());
return true;
}
catch(OrderException e)
{
// invalid order; we must exit method here
Utils.popupError(clientFrame, Utils.getLocalString(ORD_ERR_INVALID), e.getMessage());
}
return false;
}// addOrder()
/**
* Parses and then adds an order to the order list, possibly with undo/redo support,
* and only succeeding if the order passes validation given the
* validation constraints. Duplicate orders are prevented.
* <p>
* This differs from addOrderRaw in that popup dialogs are displayed
* with an error message, instead of throwing OrderExceptions.
*
* @param orderText - <b>true</b> order text to parse
* @param undoable - <b>true</b> if this is an undoable action
* @return <b>true</b> if the order was accepted
*/
public synchronized boolean addOrder(String orderText, boolean undoable)
{
// null orders not permitted.
if(orderText == null) { throw new IllegalArgumentException(); }
try
{
addOrderRaw(orderText, undoable);
orderList.clearSelection();
return true;
}
catch(OrderWarning ow)
{
// order is still acceptable (and was accepted), but issue a warning message.
Utils.popupError(clientFrame, Utils.getLocalString(ORD_ERR_AMBIGUOUS), ow.getMessage());
return true;
}
catch(OrderException e)
{
// invalid order; we must exit method here
Utils.popupError(clientFrame, Utils.getLocalString(ORD_ERR_INVALID), e.getMessage());
}
return false;
}// addOrder()
/**
* Parses and then adds an order to the TurnState,
* possibly with undo/redo support,
* and only succeeding if the order passes validation given the
* validation constraints. Duplicate orders are prevented.
*
* @param orderText - <b>true</b> Order text to parse
* @param undoable - <b>true</b> if this is an undoable action
* @throws OrderException - if the order fails validation
*/
public synchronized void addOrderRaw(String orderText, boolean undoable)
throws OrderException
{
// null orders not permitted.
if(orderText == null) { throw new IllegalArgumentException(); }
// not editable! should not be adding orders.
if(!isEditable)
{
throw new OrderException(Utils.getLocalString(ORD_ERR_UNEDITABLE));
}
// parse order
Orderable order = orderParser.parse(clientFrame.getGUIOrderFactory(),
orderText, null, turnState, false, true);
// now use addOrderRaw()
addOrderRaw(order, undoable);
}// addOrderRaw()
/**
* Add an order to the TurnState, possibly with undo/redo support,
* and only succeeding if the order passes validation given the
* validation constraints. Duplicate orders are prevented.
*
* @param order - <b>true</b> Orderable object
* @param undoable - <b>true</b> if this is an undoable action
* @throws OrderException - if the order fails validation
*/
public synchronized void addOrderRaw(Orderable order, boolean undoable)
throws OrderException
{
// null orders not permitted.
if(order == null) { throw new IllegalArgumentException(); }
// not editable! should not be adding orders.
if(!isEditable)
{
throw new OrderException(Utils.getLocalString(ORD_ERR_UNEDITABLE));
}
// not orderable! cannot add this order.
if(!isOrderable(order))
{
throw new OrderException(Utils.getLocalString(ORD_ERR_NOT_ORDERABLE));
}
// check the order; if a warning is thrown, catch and hold for later.
// an order with a warning is still acceptable.
OrderWarning orderWarning = null;
try
{
order.validate(turnState, valOpts, world.getRuleOptions());
}
catch(OrderWarning ow)
{
orderWarning = ow;
}
// check that adjustments are in-line (not too many have been issued)
checkAdjustments(order.getPower());
// note: if exception is thrown, we won't get to here.
//
// actually add/replace the order in the turnstate
// if an order already exists for this province (unit),
// the old order is returned.
Orderable replacedOrder = addOrderToTS(order);
// HOWEVER, if the new order is an exact duplicate of the old
// order, the addOrderToTS() will not replace the old order.
// to determine this, we must compare the order it found have found
// with the current order. Remember, replacedOrder may be null.
if( !order.equals(replacedOrder) )
{
if(undoable)
{
if(replacedOrder == null)
{
undoManager.addEdit(new UndoAddOrder(undoManager, order));
}
else
{
// if old order is EXACTLY equivalent to new order,
// we don't do anything;
// 2 actions involved in this case...
CompoundEdit miniEdit = new CompoundEdit();
miniEdit.addEdit(new UndoDeleteOrder(undoManager, replacedOrder));
miniEdit.addEdit(new UndoAddOrder(undoManager, order));
miniEdit.end();
undoManager.addEdit(miniEdit);
}
}
// inform everyone that an order has been created
// (and possibly an existing order has been deleted)
if(replacedOrder != null)
{
clientFrame.fireOrderDeleted(replacedOrder);
}
clientFrame.fireOrderCreated(order);
clientFrame.fireStateModified();
}
// an acceptable, but possibly ambiguous, order occured;
// the exception was held until now.
if(orderWarning != null)
{
throw orderWarning;
}
}// addOrderRaw()
/**
* Adds multiple orders to the TurnState. If failures occur, we
* return a Map of exceptions (key is order, value is the
* order exception). If no exceptions were created, a null HashMap
* is returned. All orders that do not fail are added to the TurnState.
* Warnings are also returned. If we are not in an editable state,
* an IllegalStateException is thrown. Orders that exist as duplicates
* within the TurnState are ignored.
*
* @param orders - <b>true</b> Orderable object array
* @param undoable - <b>true</b> if this is an undoable action
* @return A list of OrderExceptions, or null
*/
public synchronized java.util.Map addOrdersRaw(Orderable[] orders, boolean undoable)
{
// null orders not permitted.
if(orders == null) { throw new IllegalArgumentException(); }
// not editable! should not be adding orders.
if(!isEditable)
{
throw new IllegalStateException("not in editable state");
}
LinkedHashMap map = new LinkedHashMap(19);
ArrayList ordersAdded = new ArrayList(orders.length);
ArrayList ordersDeleted = new ArrayList(orders.length);
for(int i=0; i<orders.length; i++)
{
Orderable order = orders[i];
boolean failed = false;
try
{
order.validate(turnState, valOpts, world.getRuleOptions());
checkAdjustments(order.getPower());
}
catch(OrderWarning ow)
{
map.put(order, ow);
}
catch(OrderException oe)
{
map.put(order, oe);
failed = true;
}
if(!failed)
{
Orderable replacedOrder = addOrderToTS(order);
// check for duplicates; these are ignored
if( !order.equals(replacedOrder) )
{
ordersAdded.add(order);
if(replacedOrder != null)
{
ordersDeleted.add(replacedOrder);
}
}
}
}
if( !ordersAdded.isEmpty() ) // make sure we added at least one order
{
Orderable[] tmpDel = null;
if(ordersDeleted.size() > 0)
{
tmpDel = (Orderable[]) ordersDeleted.toArray(new Orderable[ordersAdded.size()]);
clientFrame.fireMultipleOrdersDeleted(tmpDel);
}
Orderable[] tmpAdd = (Orderable[]) ordersAdded.toArray(new Orderable[ordersAdded.size()]);
clientFrame.fireMultipleOrdersCreated(tmpAdd);
if(undoable)
{
if(tmpDel == null)
{
undoManager.addEdit(new UndoAddMultipleOrders(undoManager, tmpAdd));
}
else
{
CompoundEdit bigEdit = new CompoundEdit();
bigEdit.addEdit(new UndoDeleteMultipleOrders(undoManager, tmpDel));
bigEdit.addEdit(new UndoAddMultipleOrders(undoManager, tmpAdd));
bigEdit.end();
undoManager.addEdit(bigEdit);
}
}
clientFrame.fireStateModified();
}
return ((map.size() > 0) ? map : null);
}// addOrdersRaw()
/**
* Remove the given order from the TurnState. Fails is order is not
* given by an orderable power.
*
* @param order - <b>true</b> Orderable object
* @param undoable - <b>true</b> if this is an undoable action
* @return <b>true</b> if the order was found and removed
*/
public synchronized boolean removeOrder(Orderable order, boolean undoable)
{
if(!isOrderable(order))
{
return false;
}
boolean found = removeOrderFromTS(order);
assert(found);
if(undoable && found)
{
undoManager.addEdit(new UndoDeleteOrder(undoManager, order));
}
if(found)
{
clientFrame.fireOrderDeleted(order);
clientFrame.fireStateModified();
orderList.clearSelection();
}
return found;
}// removeOrder()
/**
* Removes the given orders. Note that if an order is not
* a member of the orderablePowers group, it will not be
* removed.
*
* @param orders - <b>true</b> Orderable object array
* @param undoable - <b>true</b> if this is an undoable action
* @return <b>true</b> if <i>all</i> orders were found and removed
*/
public synchronized boolean removeOrders(Orderable[] orders, boolean undoable)
{
int count = 0;
ArrayList deletedOrderList = new ArrayList(orders.length);
for(int i=0; i<orders.length; i++)
{
Orderable order = orders[i];
if(isOrderable(order))
{
assert(removeOrderFromTS(order));
deletedOrderList.add(order);
count++;
}
}
Orderable[] deletedOrders = (Orderable[]) deletedOrderList.toArray(new Orderable[deletedOrderList.size()]);
if(undoable)
{
if(count > 1)
{
undoManager.addEdit(new UndoDeleteMultipleOrders(undoManager, deletedOrders));
}
else if(count == 1)
{
undoManager.addEdit(new UndoDeleteOrder(undoManager, deletedOrders[0]));
}
}
if(count > 0)
{
clientFrame.fireMultipleOrdersDeleted(deletedOrders);
clientFrame.fireStateModified();
orderList.clearSelection();
}
return (count == orders.length);
}// removeOrders()
/**
* Removes all orders for all powers within the orderablePowers group
* (see ClientFrame for more information).
*
* @param undoable - <b>true</b> if this is an undoable action
*/
public synchronized void removeAllOrders(boolean undoable)
{
Orderable[] deletedOrderArray = null;
//synchronized(clientFrame.getLock())
{
// clear the orders from the turnstate.
// keep cleared orders in a temporary arraylist
ArrayList deletedOrders = new ArrayList(100);
for(int i=0; i<orderablePowers.length; i++)
{
List orders = turnState.getOrders(orderablePowers[i]);
if(orders.size() > 0)
{
deletedOrders.addAll(orders);
orders.clear();
}
}
// create a temporary order array
deletedOrderArray = (Orderable[]) deletedOrders.toArray(new Orderable[deletedOrders.size()]);
}
// if we didn't actually delete anything, don't fire or create
// any undo events.
if(deletedOrderArray.length > 0)
{
// fire!
clientFrame.fireMultipleOrdersDeleted(deletedOrderArray);
if(undoable)
{
// Use "Clear All" as a display name. Note, though, that
// it doesn't nescessarily mean that we clear *all* orders, though,
// [depends upon orderablePowers setting]
CompoundEdit ce = new UndoClearAll();
ce.addEdit(new UndoDeleteMultipleOrders(undoManager, deletedOrderArray));
ce.end();
undoManager.addEdit(ce);
}
clientFrame.fireStateModified();
}
orderList.clearSelection();
}// removeAllOrders()
/**
* Deletes the orders from the order list that are
* selected, and that are members of the orderablePowers
* group. This is <b>always</b> an undoable action.
*
*/
public synchronized void removeSelected()
{
Object[] selected = orderList.getSelectedValues();
if(selected.length == 0)
{
return;
}
Orderable[] selectedOrders = new Orderable[selected.length];
// selected objects correspond to those in the order list
// they should all be DisplayOrder objects.
for(int i=0; i<selected.length; i++)
{
selectedOrders[i] = ((DisplayOrder) selected[i]).getOrder();
assert(removeOrderFromTS(selectedOrders[i]));
}
if(selectedOrders.length == 1)
{
undoManager.addEdit(new UndoDeleteOrder(undoManager, selectedOrders[0]));
}
else
{
undoManager.addEdit(new UndoDeleteMultipleOrders(undoManager, selectedOrders));
}
clientFrame.fireMultipleOrdersDeleted(selectedOrders);
clientFrame.fireStateModified();
orderList.clearSelection();
}// removeSelected()
/** Select all orders in the list. */
public void selectAll()
{
orderList.setSelectionInterval(0, orderListModel.getSize()-1);
}// selectAll()
/** Select none of the orders in the list */
public void selectNone()
{
orderList.clearSelection();
}// selectNone()
/** Force revalidation of all orders. */
public void revalidateAllOrders()
{
orderListModel.revalidateAllOrders();
}// revalidateAllOrders()
/** Refresh / revalidate the display */
public void refresh()
{
orderList.repaint();
}// refresh()
/**
* Set how orders are sorted. A SORT_ constant must be specified
* for this to work properly. <code>null</code> values are
* not permitted.
*/
public void setSorting(String sortType, boolean reversed)
{
if(sortType != null)
{
DOComparator sortComparator = null;
if(SORT_POWER.equals(sortType))
{
sortComparator = new DOSortPower();
}
else if(SORT_PROVINCE.equals(sortType))
{
sortComparator = new DOSortProvince();
}
else if(SORT_UNIT.equals(sortType))
{
sortComparator = new DOSortUnit();
}
else if(SORT_ORDER.equals(sortType))
{
sortComparator = new DOSortOrder();
}
else
{
throw new IllegalArgumentException("unsupported type");
}
sortComparator.setAscending(!reversed);
orderListModel.sort(sortComparator);
}
else
{
throw new IllegalArgumentException();
}
}// setSorting()
/**
* Return a sort constant (identity) by parsing a sort-constant
* string. This makes sort-constant serialization safer.
* Does a case-insensitive compare. Instance equality is preserved.
* Returns the given default if parsing fails.
* <p>
* defaultValue cannot be null.
*/
public static String parseSortValue(String in, String defaultValue)
{
if(defaultValue != SORT_POWER
&& defaultValue != SORT_PROVINCE
&& defaultValue != SORT_ORDER
&& defaultValue != SORT_UNIT
&& defaultValue != null)
{
throw new IllegalArgumentException();
}
if(SORT_POWER.equalsIgnoreCase(in))
{
return SORT_POWER;
}
else if(SORT_PROVINCE.equalsIgnoreCase(in))
{
return SORT_PROVINCE;
}
else if(SORT_ORDER.equalsIgnoreCase(in))
{
return SORT_ORDER;
}
else if(SORT_UNIT.equalsIgnoreCase(in))
{
return SORT_UNIT;
}
return defaultValue;
}// parseSortValue()
/**
* Overriden to return the preferred size. This ensures that
* we resize properly in a JSplitPane.
*/
public Dimension getMinimumSize()
{
return new Dimension(getPreferredSize());
}// getMinimumSize()
/**
* Adds an order to the TurnState. If an order for that province
* already exists, it is deleted, and returned by that message.
* If no order exists for the province, the new order is added,
* and we return null. If an order for a province exist,
* only the first order encountered in list traversal will be
* deleted.
* <p>
* <b>NOTE:</b> If the new order to add is the same (via equals())
* as the order we are replacing, the turnstate is not modified.
* The old order is still returned. Thus if the passed order is
* equal to the returned order, it was an exact duplicate.
* <p>
* The main reason for this behavior is reduce rendering events
* and undo/redo actions.
*
*/
private Orderable addOrderToTS(Orderable order)
{
boolean isDuplicate = false;
Orderable replacedOrder = null;
//synchronized(clientFrame.getLock())
{
List orders = turnState.getOrders(order.getPower());
Iterator iter = orders.iterator();
while(iter.hasNext())
{
Orderable listOrder = (Orderable) iter.next();
if( listOrder.getSource().isProvinceEqual(order.getSource()) )
{
replacedOrder = listOrder;
isDuplicate = listOrder.equals(order);
if(!isDuplicate)
{
iter.remove();
}
break;
}
}
// don't add a duplicate order, since we didn't remove it!
if(!isDuplicate)
{
orders.add(order);
}
}
return replacedOrder;
}// addOrderToTS()
/**
* Removes an order from the TurnState. If duplicate orders
* for a unit (province) exist, only the first will be removed.
* This returns <b>true</b> if the order was found and removed.
*/
private boolean removeOrderFromTS(Orderable order)
{
//synchronized(clientFrame.getLock())
{
List orders = turnState.getOrders(order.getPower());
return orders.remove(order);
}
}// removeOrderFromTS()
/**
* If power is not in the list of Orderable powers, return false.
* Also return false if turnstate has been resolved.
*/
private boolean isOrderable(Orderable order)
{
// we are reviewing orders
if(turnState.isResolved())
{
return false;
}
for(int i=0; i<orderablePowers.length; i++)
{
if(orderablePowers[i] == order.getPower())
{
return true;
}
}
return false;
}// isOrderable()
/**
* Checks if player has issued too many build or remove orders; if so,
* throws an OrderException.
* <p>
* Note that there is no way to check against too few adjustment orders being
* issued, and that issuing too few Build orders is acceptable.
* <p>
* This only works during the Adjustment phase.
*/
private void checkAdjustments(Power power)
throws OrderException
{
if(adjMap != null)
{
Adjustment.AdjustmentInfo adjInfo = adjMap.get(power);
int numOrders = turnState.getOrders(power).size();
int max = Math.abs( adjInfo.getAdjustmentAmount() );
if(numOrders >= max)
{
String dlgtext = MessageFormat.format( Utils.getText(Utils.getLocalString(DLG_TOOMANY_TEXT_LOCATION)),
new Object[] {power, new Integer(max)} );
throw new OrderException(dlgtext);
}
}
}// checkAdjustments()
/**
* Property change listener
*
*/
protected class ODPPropertyListener extends AbstractCFPListener
{
public void actionOrderCreated(Orderable order)
{
orderListModel.addOrder(order);
}// actionOrderCreated()
public void actionOrderDeleted(Orderable order)
{
orderListModel.removeOrder(order);
}// actionOrderDeleted()
public void actionOrdersCreated(Orderable[] orders)
{
orderListModel.addOrders(orders);
}// actionOrdersCreated()
public void actionOrdersDeleted(Orderable[] orders)
{
orderListModel.removeOrders(orders);
}// actionOrdersDeleted()
public void actionOrderablePowersChanged(Power[] oldPowers, Power[] newPowers)
{
orderablePowers = newPowers;
}// actionOrderablePowersChanged()
public void actionDisplayablePowersChanged(Power[] oldPowers, Power[] newPowers)
{
displayablePowers = newPowers;
if(turnState != null)
{
orderListModel.updateFromTurnState();
}
}// actionDisplayablePowersChanged()
public void actionValOptsChanged(ValidationOptions options)
{
valOpts = options;
if(turnState != null)
{
orderListModel.revalidateAllOrders();
}
}// actionValOptsChanged()
public synchronized void actionWorldCreated(World w)
{
world = w;
undoManager = clientFrame.getUndoRedoManager();
valOpts = clientFrame.getValidationOptions();
}// actionWorldCreated()
public void actionWorldDestroyed(World w)
{
orderListModel.removeAllOrders();
orderListModel.setSortComparator(new DOSortProvince());
// we are now entering a dangerous state
turnState = null;
world = null;
displayablePowers = null;
orderablePowers = null;
}// actionWorldDestroyed()
public void actionTurnstateChanged(TurnState ts)
{
turnState = ts;
if(turnState.getPhase().getPhaseType() == Phase.PhaseType.ADJUSTMENT)
{
adjMap = Adjustment.getAdjustmentInfo(turnState,
world.getRuleOptions(), world.getMap().getPowers());
}
else
{
adjMap = null;
}
orderListModel.updateFromTurnState();
orderList.clearSelection();
}// actionTurnstateChanged()
public synchronized void actionModeChanged(String newMode)
{
if(newMode == ClientFrame.MODE_ORDER)
{
isEditable = true;
orderList.setEnabled(true);
orderList.clearSelection();
orderablePowers = clientFrame.getOrderablePowers();
displayablePowers = clientFrame.getDisplayablePowers();
if(turnState != null)
{
orderListModel.updateFromTurnState();
}
}
else if(newMode == ClientFrame.MODE_EDIT
|| newMode == ClientFrame.MODE_NONE)
{
isEditable = false;
orderList.setEnabled(false);
orderList.clearSelection();
}
else
{
isEditable = false;
displayablePowers = world.getMap().getPowers();
orderablePowers = new Power[0];
orderList.setEnabled(false);
orderList.clearSelection();
}
orderListModel.activateMenu();
if(undoManager != null)
{
undoManager.refreshMenu();
}
}// actionModeChanged()
}// inner class ODPPropertyListener
/**
* Keeps a the list of orders that is displayed in the order list.
* Handles sorting and updating of the list. No protection for
* duplicate DisplayOrders occurs here. However, we do take the
* displayablePowers into account.
*/
private class OrderListModel extends AbstractListModel
{
private ArrayList list;
private DOComparator comparator;
/** Create an OrderListModel object */
public OrderListModel()
{
list = new ArrayList(50);
comparator = new DOSortProvince(); // default comparator
}// OrderListModel()
/** Return the Size of the list. */
public int getSize()
{
return list.size();
}// getSize()
/** Returns the object (DisplayOrder) at the given index. */
public Object getElementAt(int index)
{
return list.get(index);
}// getElementAt()
/** Set the Sort Comparator. Null comparators are not allowed. */
public void setSortComparator(DOComparator comp)
{
if(comp == null)
{
throw new IllegalArgumentException("null comp");
}
synchronized(this)
{
this.comparator = comp;
}
}// setSortComparator()
/**
* Public, synchronized sort and update. This method
* sets the comparator as well; if the same type of
* comparator has already been set, it reverses
* the direction sort direction.
* <p>
* A null comparator is not allowed.
*/
public void sort(DOComparator comp)
{
if(comp == null)
{
throw new IllegalArgumentException("null comp");
}
synchronized(this)
{
if(comparator.equals(comp))
{
comparator.setAscending( !comparator.isAscending() );
}
else
{
this.comparator = comp;
}
}
synchronized(list)
{
sort();
}
}// sort()
/** Sort the list, and fires a ContentsChanged message so the list is updated. */
private void sort()
{
synchronized(this)
{
Collections.sort(list, comparator); // sort
comparator.setHighlighting(list.iterator()); // mark hilites
fireContentsChanged(this, 0, getSize() - 1); // update entire list
activateMenu();
}
}// sort()
/** Adds a DisplayOrder to the list. Only an order
* that is in the list of Powers that are allowed to be displayed
* will be added to the list.
*/
public void addOrder(Orderable order)
{
if(isDisplayable(order))
{
synchronized(list)
{
list.add(createDisplayOrder(order));
sort();
}
}
}// addDisplayOrder()
/**
* Adds multiple DisplayOrders to the list. Only orders
* that are in the list of Powers that are allowed to be displayed
* are added to the list.
*/
public void addOrders(Orderable[] orders)
{
int addCount = 0;
synchronized(list)
{
for(int i=0; i<orders.length; i++)
{
if(isDisplayable(orders[i]))
{
list.add(createDisplayOrder(orders[i]));
addCount++;
}
}
// no updating required iff no orders added
if(addCount > 0)
{
sort();
}
}
}// addDisplayOrders()
/** Removes all DisplayOrders matching this order. */
public void removeOrder(Orderable order)
{
// note: if duplicates are present, this will remove duplicates
// no displayed-power checking is required here
synchronized(list)
{
Iterator iter = list.iterator();
while(iter.hasNext())
{
DisplayOrder displayOrder = (DisplayOrder) iter.next();
if(displayOrder.getOrder() == order)
{
iter.remove();
}
}
sort();
}
}// removeDisplayOrder()
/** Removes all DisplayOrder matching the given array of orders. */
public void removeOrders(final Orderable[] orders)
{
// note: if duplicates are present, this will remove duplicates
// no displayed-power checking is required here
synchronized(list)
{
Iterator iter = list.iterator();
while(iter.hasNext())
{
DisplayOrder displayOrder = (DisplayOrder) iter.next();
final Orderable doOrder = displayOrder.getOrder();
for(int i=0; i<orders.length; i++)
{
if(doOrder == orders[i])
{
iter.remove();
}
}
}
sort();
}
}// removeDisplayOrders()
/** Removes all DisplayOrders from the list */
public void removeAllOrders()
{
synchronized(list)
{
list.clear();
sort();
}
}// removeAllDisplayOrders()
/** Update displayed powers from TurnState*/
public void updateFromTurnState()
{
// when the displayed powers change, we need to completely
// recreate the displayed power list from the turnstate, adding
// only the 'allowed' powers.
//
assert(turnState != null);
synchronized(list)
{
list.clear();
Iterator iter = turnState.getAllOrders().iterator();
while(iter.hasNext())
{
Orderable order = (Orderable) iter.next();
if(isDisplayable(order))
{
list.add(createDisplayOrder(order));
}
}
sort();
}
}// updateFromTurnState()
/** Revalidate all displayed orders */
public void revalidateAllOrders()
{
synchronized(list)
{
Iterator iter = list.iterator();
while(iter.hasNext())
{
DisplayOrder displayOrder = (DisplayOrder) iter.next();
try
{
displayOrder.getOrder().validate(turnState, valOpts, world.getRuleOptions());
displayOrder.setInvalid(false);
}
catch(OrderException e)
{
displayOrder.setInvalid(true);
}
}
sort();
}
}// revalidateAllOrders()
/**
* Activates the edit menu, depending on if we are in an
* editable state and if there are any items in the order list.
*/
public void activateMenu()
{
clientFrame.getClientMenu().setEditItemsEnabled(((getSize() > 0) && isEditable));
}// activateMenu()
/** Checks if an Order is in the Displayed Power array. */
private boolean isDisplayable(final Orderable order)
{
for(int i=0; i<displayablePowers.length; i++)
{
if(order.getPower() == displayablePowers[i])
{
return true;
}
}
return false;
}// isDisplayable()
/**
* Creates a DisplayOrder and sets whether it is valid
* and/or if it has failed.
*/
private DisplayOrder createDisplayOrder(Orderable order)
{
DisplayOrder displayOrder = new DisplayOrder(order);
// validate
try
{
order.validate(turnState, valOpts, world.getRuleOptions());
}
catch(OrderException e)
{
displayOrder.setInvalid(true);
}
// see if failed [only applies when turnstate is resolved]
if(turnState.isResolved())
{
displayOrder.setFailed( !turnState.isOrderSuccessful(order) );
}
return displayOrder;
}// createDisplayOrder()
}// inner class OrderListModel
/**
* Custom List Renderer that is used to render the background
* highlights created by sorting.
*/
private class OrderListRenderer extends DefaultListCellRenderer
{
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus)
{
Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
// set background color, if we are not selected
if(!isSelected)
{
if( ((DisplayOrder) value).isHighlighted() )
{
setBackground(BG_HILITE);
}
else
{
setBackground(BG_DEFAULT);
}
}
// set text, w/ or w/o conversion, depending upon if we are unicode-aware
Font f = component.getFont();
if(!f.canDisplay('\u2192'))
{
// search for unicode-arrow; replace with "->"
String text = ((JLabel) this).getText();
if(text != null)
{
StringBuffer buffer = new StringBuffer(text);
boolean isChanged = false;
for(int i=buffer.length()-1; i>=0; i--)
{
final char c = buffer.charAt(i);
if(c == '\u2192')
{
buffer.deleteCharAt(i);
buffer.insert(i, "->");
isChanged = true;
}
}
if(isChanged)
{
((JLabel) this).setText(buffer.toString());
}
}
}
return component;
}// getListCellRendererComponent()
/** Overridden for performance */
public void invalidate() {}
/** Overriden for performance */
public void repaint() {}
}// inner class OrderListRenderer
/**
* One DisplayOrder object is created for each order in the
* TurnState that is displayed (i.e., in displayablePowers).
* <p>
* The Orderable object of a DisplayOrder is immutable.
*/
private final class DisplayOrder
{
// instance variables
private final Orderable order;
private boolean isInvalid = false;
private boolean isFailed = false;
private boolean isHilite = false;
/** Create a DisplayOrder */
public DisplayOrder(Orderable order)
{
if(order == null)
{
throw new IllegalArgumentException("null order");
}
this.order = order;
}// DisplayOrder()
/** Get the Order */
public Orderable getOrder() { return order; }
/** Set if the order is invalid (failed validation) */
public void setInvalid(boolean value) { isInvalid = value; }
/** Returns if the order is invalid (failed validation) */
public boolean isInvalid() { return isInvalid; }
/** Set if the order should be highlighted */
public void setHighlighted(boolean value) { isHilite = value; }
/** Returns if the order should be highlighted */
public boolean isHighlighted() { return isHilite; }
/** Set if the order failed */
public void setFailed(boolean value) { isFailed = value; }
/** Return HTML formatted text to display. */
public String toString()
{
StringBuffer sb = new StringBuffer(128);
sb.append("<html>");
if(isInvalid)
{
sb.append("<font color=\"#EE0000\">");
}
if(isFailed)
{
sb.append("<u>");
}
sb.append(order.toFormattedString(clientFrame.getOFO()));
// we do not put closing tags for <font>, <u>, or <html>
return sb.toString();
}// toString()
}// inner class DisplayOrder
/** DOComparator class offers highlight-marking support */
private abstract class DOComparator implements Comparator
{
private boolean isAscending = true;
/** Return the object we need to compare to make setHighlighting work. */
protected abstract Object getComparisonObject(DisplayOrder displayedOrder);
/** The method we need to make compare() work */
protected abstract int compareDisplayOrders(DisplayOrder do1, DisplayOrder do2);
/**
* The compare method. This essentially returns the result of
* compareDisplayOrders() unless the sort is reversed.
*/
public final int compare(Object o1, Object o2)
{
final int result = compareDisplayOrders((DisplayOrder) o1, (DisplayOrder) o2);
return ((isAscending) ? result : -result);
}// compare()
/** Sets the sort direction. Ascending by default. */
public final void setAscending(boolean value) { isAscending = value; }
/** Returns if the sort order is ascending or not */
public final boolean isAscending() { return isAscending; }
/**
* Mark the highlighted items in a collection of orders.
* NOTE: the Iterator must return DisplayOrder objects.
*/
public void setHighlighting(Iterator iter)
{
boolean toHilite = true;
Object lastObject = null;
if(iter.hasNext())
{
DisplayOrder first = (DisplayOrder) iter.next();
first.setHighlighted(toHilite);
lastObject = getComparisonObject(first);
}
while(iter.hasNext())
{
DisplayOrder next = (DisplayOrder) iter.next();
Object nextObject = getComparisonObject(next);
if( !lastObject.equals(nextObject) )
{
toHilite = !toHilite;
lastObject = nextObject;
}
next.setHighlighted(toHilite);
}
}// setHighlighting()
}// inner abstract class DOComparator
/** Comparator that sorts DisplayOrder by Power */
private class DOSortPower extends DOComparator
{
/** Determine if we are the same Comparator type */
public boolean equals(Object obj)
{
return (obj instanceof DOSortPower);
}// equals()
/**
* DOComparator Implementation. Passed parameters are
* assumed to be DisplayOrder objects.
*/
protected int compareDisplayOrders(DisplayOrder do1, DisplayOrder do2)
{
Power p1 = do1.getOrder().getPower();
Power p2 = do2.getOrder().getPower();
return p1.compareTo(p2);
}// compare()
protected Object getComparisonObject(DisplayOrder displayedOrder)
{
return displayedOrder.getOrder().getPower();
}// getComparisonObject()
}// inner class DOSortPower
/** Comparator that sorts DisplayOrder by Province */
private class DOSortProvince extends DOComparator
{
/** Determine if we are the same Comparator type */
public boolean equals(Object obj)
{
return (obj instanceof DOSortProvince);
}// equals()
/** DOComparator Implementation. */
protected int compareDisplayOrders(DisplayOrder do1, DisplayOrder do2)
{
Province pr1 = do1.getOrder().getSource().getProvince();
Province pr2 = do2.getOrder().getSource().getProvince();
return pr1.compareTo(pr2);
}// compare()
/** DOComparator Implementation. */
protected Object getComparisonObject(DisplayOrder displayedOrder)
{
return displayedOrder.getOrder().getSource().getProvince();
}// getComparisonObject()
}// inner class DOSortProvince
/** Comparator that sorts DisplayOrder by Unit */
private class DOSortUnit extends DOComparator
{
/** Determine if we are the same Comparator type */
public boolean equals(Object obj)
{
return (obj instanceof DOSortUnit);
}// equals()
/** DOComparator Implementation. */
protected int compareDisplayOrders(DisplayOrder do1, DisplayOrder do2)
{
String name1 = do1.getOrder().getSourceUnitType().getFullName();
String name2 = do2.getOrder().getSourceUnitType().getFullName();
return name1.compareTo(name2);
}// compare()
/** DOComparator Implementation. */
protected Object getComparisonObject(DisplayOrder displayedOrder)
{
return displayedOrder.getOrder().getSourceUnitType();
}// getComparisonObject()
}// inner class DOSortUnit
/** Comparator that sorts DisplayOrder by Order Type */
private class DOSortOrder extends DOComparator
{
/** Determine if we are the same Comparator type */
public boolean equals(Object obj)
{
return (obj instanceof DOSortOrder);
}// equals()
/** DOComparator Implementation. */
protected int compareDisplayOrders(DisplayOrder do1, DisplayOrder do2)
{
String ordName1 = do1.getOrder().getFullName();
String ordName2 = do2.getOrder().getFullName();
return ordName1.compareTo(ordName2);
}// compare()
/** DOComparator Implementation. */
protected Object getComparisonObject(DisplayOrder displayedOrder)
{
return displayedOrder.getOrder().getBriefName();
}// getComparisonObject()
}// inner class DOSortOrder
/**
* Perform layout, and create GUI elements for
* sort buttons. No border is created around
* the OrderDisplayPanel. Note that this is called
* by the constructor.
*/
protected void makeLayout()
{
// start layout
int w1[] = { 0 };
int h1[] = { 0, 5, 0 }; // 3 pixels between scroll list & sort buttons
HIGLayout hl = new HIGLayout(w1, h1);
hl.setColumnWeight(1, 1);
hl.setRowWeight(1, 1);
setLayout(hl);
HIGConstraints c = new HIGConstraints();
add(orderListScrollPane, c.rc(1,1,"lrtb"));
add(makeSortPanel(), c.rc(3,1));
}// makeLayout()
/**
* Makes the panel containing the sort buttons.
* Defines actions for these buttons as well.
*/
protected JPanel makeSortPanel()
{
// label
JLabel label = new JLabel(Utils.getLocalString(LABEL_SORT));
// combobox
JComboBox sortCombo = new JComboBox();
sortCombo.setEditable(false);
sortCombo.addItem(Utils.getLocalString(LABEL_SORT_POWER));
sortCombo.addItem(Utils.getLocalString(LABEL_SORT_PROVINCE));
sortCombo.addItem(Utils.getLocalString(LABEL_SORT_UNIT));
sortCombo.addItem(Utils.getLocalString(LABEL_SORT_ORDER));
sortCombo.setSelectedItem(Utils.getLocalString(LABEL_SORT_PROVINCE));
sortCombo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
String item = (String) ((JComboBox) e.getSource()).getSelectedItem();
if(item == Utils.getLocalString(LABEL_SORT_POWER))
{
orderListModel.sort(new DOSortPower());
}
else if(item == Utils.getLocalString(LABEL_SORT_PROVINCE))
{
orderListModel.sort(new DOSortProvince());
}
else if(item == Utils.getLocalString(LABEL_SORT_UNIT))
{
orderListModel.sort(new DOSortUnit());
}
else if(item == Utils.getLocalString(LABEL_SORT_ORDER))
{
orderListModel.sort(new DOSortOrder());
}
}
});
// layout
JPanel sortPanel = new JPanel(null);
sortPanel.setLayout(new BoxLayout(sortPanel, BoxLayout.X_AXIS));
sortPanel.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
sortPanel.add(label);
sortPanel.add(Box.createHorizontalStrut(5));
sortPanel.add(sortCombo);
return sortPanel;
}// makeSortPanel()
}// class OrderDisplayPanel