/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.control;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.freecol.FreeCol;
import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.GUI;
import net.sf.freecol.client.gui.animation.Animations;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.client.gui.option.FreeColActionUI;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.DiplomaticTrade.TradeStatus;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.FreeColObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.HistoryEvent;
import net.sf.freecol.common.model.IndianNationType;
import net.sf.freecol.common.model.LastSale;
import net.sf.freecol.common.model.ModelMessage;
import net.sf.freecol.common.model.Monarch.MonarchAction;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Player.Stance;
import net.sf.freecol.common.model.Region;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TradeRoute;
import net.sf.freecol.common.model.Turn;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.networking.ChatMessage;
import net.sf.freecol.common.networking.ChooseFoundingFatherMessage;
import net.sf.freecol.common.networking.Connection;
import net.sf.freecol.common.networking.DOMMessage;
import net.sf.freecol.common.networking.DiplomacyMessage;
import net.sf.freecol.common.networking.IndianDemandMessage;
import net.sf.freecol.common.networking.LootCargoMessage;
import net.sf.freecol.common.networking.MonarchActionMessage;
import net.sf.freecol.common.networking.NewLandNameMessage;
import net.sf.freecol.common.networking.NewRegionNameMessage;
import org.freecolandroid.repackaged.javax.swing.SwingUtilities;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Handles the network messages that arrives while in the getGame().
*/
public final class InGameInputHandler extends InputHandler {
private static final Logger logger = Logger.getLogger(InGameInputHandler.class.getName());
private Unit lastAnimatedUnit = null;
/**
* The constructor to use.
*
* @param freeColClient The main controller.
*/
public InGameInputHandler(FreeColClient freeColClient, GUI gui) {
super(freeColClient, gui);
}
/**
* Deals with incoming messages that have just been received.
*
* @param connection The <code>Connection</code> the message was received
* on.
* @param element The root element of the message.
* @return The reply.
*/
@Override
public Element handle(Connection connection, Element element) {
if (element == null) {
throw new RuntimeException("Received empty (null) message!");
}
Element reply;
String type = element.getTagName();
logger.log(Level.FINEST, "Received message: " + type);
if (type.equals("update")) {
reply = update(element);
} else if (type.equals("remove")) {
reply = remove(element);
} else if (type.equals("animateMove")) {
reply = animateMove(element);
} else if (type.equals("animateAttack")) {
reply = animateAttack(element);
} else if (type.equals("setCurrentPlayer")) {
reply = setCurrentPlayer(element);
} else if (type.equals("newTurn")) {
reply = newTurn(element);
} else if (type.equals("setDead")) {
reply = setDead(element);
} else if (type.equals("gameEnded")) {
reply = gameEnded(element);
} else if (type.equals("chat")) {
reply = chat(element);
} else if (type.equals("disconnect")) {
reply = disconnect(element);
} else if (type.equals("error")) {
reply = error(element);
} else if (type.equals("chooseFoundingFather")) {
reply = chooseFoundingFather(element);
} else if (type.equals("indianDemand")) {
reply = indianDemand(element);
} else if (type.equals("spyResult")) {
reply = spyResult(element);
} else if (type.equals("reconnect")) {
reply = reconnect(element);
} else if (type.equals("setAI")) {
reply = setAI(element);
} else if (type.equals("monarchAction")) {
reply = monarchAction(element);
} else if (type.equals("setStance")) {
reply = setStance(element);
} else if (type.equals("diplomacy")) {
reply = diplomacy(element);
} else if (type.equals("addPlayer")) {
reply = addPlayer(element);
} else if (type.equals("addObject")) {
reply = addObject(element);
} else if (type.equals("newLandName")) {
reply = newLandName(element);
} else if (type.equals("newRegionName")) {
reply = newRegionName(element);
} else if (type.equals("fountainOfYouth")) {
reply = fountainOfYouth(element);
} else if (type.equals("lootCargo")) {
reply = lootCargo(element);
} else if (type.equals("closeMenus")) {
reply = closeMenus();
} else if (type.equals("multiple")) {
reply = multiple(connection, element);
} else {
logger.warning("Unsupported message type: " + type);
return null;
}
logger.log(Level.FINEST, "Handled message: " + type
+ " replying with: "
+ ((reply == null) ? "null" : reply.getTagName()));
final FreeColClient fcc = getFreeColClient();
if (Boolean.TRUE.toString().equals(element.getAttribute("flush"))
&& fcc.currentPlayerIsMyPlayer()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fcc.getInGameController().displayModelMessages(false);
}
});
}
return reply;
}
/**
* Handles an "reconnect"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element reconnect(Element element) {
logger.finest("Entered reconnect...");
if (new ShowConfirmDialogSwingTask(null,
StringTemplate.key("reconnect.text"),
"reconnect.yes", "reconnect.no").confirm()) {
logger.finest("User wants to reconnect, do it!");
new ReconnectSwingTask().invokeLater();
} else {
// This fairly drastic operation can be done in any thread,
// no need to use SwingUtilities.
logger.finest("No reconnect, quit.");
getFreeColClient().quit();
}
return null;
}
/**
* Handles an "update"-message.
*
* @param updateElement The element (root element in a DOM-parsed XML tree)
* that holds all the information.
* @return The reply.
*/
public Element update(Element updateElement) {
updateGameObjects(updateElement.getChildNodes());
new RefreshCanvasSwingTask().invokeLater();
return null;
}
/**
* Updates all FreeColGameObjects from the childNodes of the message
*
* @param nodeList The list of nodes from the message
*/
private void updateGameObjects(NodeList nodeList) {
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
String id = element.getAttribute(FreeColObject.ID_ATTRIBUTE);
FreeColGameObject fcgo = getGame().getFreeColGameObjectSafely(id);
if (fcgo == null) {
logger.warning("Object in update not present in client: " + id);
} else {
fcgo.readFromXMLElement(element);
}
}
}
/**
* Handles a "remove"-message.
*
* @param removeElement The element (root element in a DOM-parsed XML tree)
* that holds all the information.
*/
private Element remove(Element removeElement) {
Game game = getGame();
String divertString = removeElement.getAttribute("divert");
FreeColGameObject divert
= (divertString == null || divertString.isEmpty()) ? null
: game.getFreeColGameObject(divertString);
Player player = getFreeColClient().getMyPlayer();
NodeList nodeList = removeElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
String idString = element.getAttribute(FreeColObject.ID_ATTRIBUTE);
FreeColGameObject fcgo
= (idString == null || idString.isEmpty()) ? null
: game.getFreeColGameObject(idString);
if (fcgo == null) {
logger.warning("Could not find FreeColGameObject with ID: "
+ idString);
} else {
if (divert != null) {
player.divertModelMessages(fcgo, divert);
}
// Deselect the object if it is the current active unit.
if (fcgo instanceof Unit) {
Unit u = (Unit) fcgo;
player.invalidateCanSeeTiles();
if (u == gui.getActiveUnit())
gui.setActiveUnit(null);
// Temporary hack until we have real containers.
player.removeUnit(u);
}
// Do just the low level dispose that removes
// reference to this object in the client. The other
// updates should have done the rest.
fcgo.fundamentalDispose();
}
}
new RefreshCanvasSwingTask().invokeLater();
return null;
}
/**
* Sometimes units appear which the client does not know about,
* and are passed in as the children of the parent element.
*
* @param game The <code>Game</code> to add the unit to.
* @param element The <code>Element</code> to find a unit in.
* @param id The id of the unit to find.
* @return A unit or null if none found.
*/
private static Unit selectUnitFromElement(Game game, Element element,
String id) {
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Element e = (Element) nodes.item(i);
if (id.equals(e.getAttribute(FreeColObject.ID_ATTRIBUTE))) {
return new Unit(game, e);
}
}
return null;
}
/**
* Handles an "animateMove"-message. This only performs animation,
* if required. It does not actually change unit positions, which
* happens in an "update".
*
* @param element An element (root element in a DOM-parsed XML tree) that
* holds attributes for the old and new tiles and an element for
* the unit that is moving (which are used solely to operate the
* animation).
*/
private Element animateMove(Element element) {
FreeColClient client = getFreeColClient();
Game game = getGame();
String unitId = element.getAttribute("unit");
Unit unit;
if (unitId == null
|| ((unit = (Unit) game.getFreeColGameObjectSafely(unitId)) == null
&& (unit = selectUnitFromElement(game, element, unitId)) == null)) {
throw new IllegalStateException("Animation"
+ " for: " + client.getMyPlayer().getId()
+ " missing unit:" + unitId);
}
if (!client.isHeadless()
&& Animations.getAnimationSpeed(getFreeColClient(), unit) > 0) {
String oldTileId = element.getAttribute("oldTile");
String newTileId = element.getAttribute("newTile");
Tile oldTile, newTile;
if (oldTileId == null
|| (oldTile = (Tile) game.getFreeColGameObjectSafely(oldTileId)) == null) {
throw new IllegalStateException("Amimation"
+ " for: " + client.getMyPlayer().getId()
+ " missing oldTile: " + oldTileId);
}
if (newTileId == null
|| (newTile = (Tile) game.getFreeColGameObjectSafely(newTileId)) == null) {
throw new IllegalStateException("Animation"
+ " for: " + client.getMyPlayer().getId()
+ " missing newTile: " + newTileId);
}
// All is well, queue the animation.g
// Use lastAnimatedUnit as a filter to avoid excessive refocussing.
try {
new UnitMoveAnimationCanvasSwingTask(unit, oldTile, newTile,
unit != lastAnimatedUnit)
.invokeSpecial();
lastAnimatedUnit = unit;
} catch (Exception e) {
logger.log(Level.WARNING, "UnitMoveAnimation", e);
}
}
return null;
}
/**
* Handles an "animateAttack"-message. This only performs animation, if
* required. It does not actually perform any attacks.
*
* @param element An element (root element in a DOM-parsed XML tree) that
* holds attributes for the old and new tiles and an element for
* the unit that is moving (which are used solely to operate the
* animation).
*/
private Element animateAttack(Element element) {
FreeColClient client = getFreeColClient();
if (client.isHeadless()) return null;
Game game = getGame();
String attackerId = element.getAttribute("attacker");
Unit attacker = (Unit) game.getFreeColGameObjectSafely(attackerId);
if (attacker == null
&& (attacker = selectUnitFromElement(game, element,
attackerId)) == null) {
logger.warning("Attack animation"
+ " for: " + client.getMyPlayer().getId()
+ " incorrectly omitted attacker: " + attackerId);
return null;
}
String defenderId = element.getAttribute("defender");
Unit defender = (Unit) game.getFreeColGameObjectSafely(defenderId);
if (defender == null
&& (defender = selectUnitFromElement(game, element,
defenderId)) == null) {
logger.warning("Attack animation"
+ " for: " + client.getMyPlayer().getId()
+ " incorrectly omitted defender: " + defenderId);
return null;
}
boolean success = Boolean.parseBoolean(element.getAttribute("success"));
if (attacker == null || defender == null) {
throw new IllegalStateException("animateAttack"
+ ((attacker == null) ? ": null attacker" : "")
+ ((defender == null) ? ": null defender" : ""));
}
// All is well, queue the animation.
// Use lastAnimatedUnit as a filter to avoid excessive refocussing.
try {
new UnitAttackAnimationCanvasSwingTask(attacker, defender, success,
attacker != lastAnimatedUnit)
.invokeSpecial();
} catch (Exception exception) {
logger.warning("UnitAttackAnimationCanvasSwingTask raised "
+ exception.toString());
}
lastAnimatedUnit = attacker;
return null;
}
private void takeTurn(Player player, boolean newTurn) {
final FreeColClient fcc = getFreeColClient();
fcc.getInGameController().setCurrentPlayer(player);
if (newTurn) {
List<Settlement> settlements = player.getSettlements();
Tile defTile = ((settlements.size() > 0)
? settlements.get(0).getTile()
: player.getEntryLocation().getTile()).getSafeTile(null, null);
player.resetIterators();
fcc.getInGameController().nextActiveUnit(defTile);
}
fcc.getActionManager().update();
}
/**
* Handles a "setCurrentPlayer"-message.
*
* @param element The element (root element in a DOM-parsed XML
* tree) that holds all the information.
* @return an <code>Element</code> value
*/
private Element setCurrentPlayer(Element element) {
final FreeColClient fcc = getFreeColClient();
final Player player = fcc.getMyPlayer();
final Player newPlayer = (Player) getGame()
.getFreeColGameObject(element.getAttribute("player"));
final boolean newTurn = player.equals(newPlayer);
if (FreeCol.isInDebugMode()
&& fcc.currentPlayerIsMyPlayer()) closeMenus();
if (FreeCol.isDebugRunComplete()
&& FreeCol.getDebugRunSaveName() != null) {
try {
fcc.getFreeColServer().saveGame(new File(".",
FreeCol.getDebugRunSaveName()),
fcc.getMyPlayer().getName(),
fcc.getClientOptions());
} catch (IOException e) {}
fcc.quit();
}
if (SwingUtilities.isEventDispatchThread()) {
takeTurn(newPlayer, newTurn);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
takeTurn(newPlayer, newTurn);
}
});
} catch (InterruptedException e) {
// Ignore
} catch (InvocationTargetException e) {
// Ignore
}
}
new RefreshCanvasSwingTask(true).invokeLater();
return null;
}
/**
* Handles a "newTurn"-message.
*
* @param element The element (root element in a DOM-parsed XML tree)
* that holds all the information.
*/
private Element newTurn(Element element) {
Game game = getGame();
String turnString = element.getAttribute("turn");
try {
int turnNumber = Integer.parseInt(turnString);
game.setTurn(new Turn(turnNumber));
} catch (NumberFormatException e) {
logger.warning("Bad turn in newTurn: " + turnString);
}
Turn currTurn = game.getTurn();
// plays an alert sound on each new turn if the option for it is turned on
if (getFreeColClient().getClientOptions().getBoolean("model.option.audioAlerts")) {
gui.playSound("sound.event.alertSound");
}
if (currTurn.isFirstSeasonTurn()) {
new ShowInformationMessageSwingTask(StringTemplate.key("twoTurnsPerYear")).invokeLater();
}
new UpdateMenuBarSwingTask().invokeLater();
return null;
}
/**
* Handles a "setDead"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element setDead(Element element) {
FreeColClient freeColClient = getFreeColClient();
Player player = (Player) getGame().getFreeColGameObject(element.getAttribute("player"));
Player myPlayer = freeColClient.getMyPlayer();
if (player == myPlayer) {
if (freeColClient.isSingleplayer()) {
if (myPlayer.getPlayerType() != Player.PlayerType.UNDEAD
&& new ShowConfirmDialogSwingTask(null,
StringTemplate.key("defeatedSingleplayer.text"),
"defeatedSingleplayer.yes",
"defeatedSingleplayer.no").confirm()) {
freeColClient.askServer().enterRevengeMode();
} else {
freeColClient.quit();
}
} else {
if (!new ShowConfirmDialogSwingTask(null, StringTemplate.key("defeated.text"),
"defeated.yes", "defeated.no").confirm()) {
freeColClient.quit();
}
}
} else {
myPlayer.setStance(player, null);
}
return null;
}
/**
* Handles a "gameEnded"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element gameEnded(Element element) {
FreeColClient freeColClient = getFreeColClient();
Player winner = (Player) getGame().getFreeColGameObject(element.getAttribute("winner"));
if (winner == freeColClient.getMyPlayer()) {
new ShowVictoryPanelSwingTask().invokeLater();
} // else: The client has already received the message of defeat.
return null;
}
/**
* Handles a "chat"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element chat(Element element) {
final ChatMessage chatMessage = new ChatMessage(getGame(), element);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gui.displayChatMessage(chatMessage.getMessage(),
chatMessage.isPrivate());
}
});
return null;
}
/**
* Handles an "error"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element error(Element element) {
try {
new ShowErrorMessageSwingTask(element.getAttribute("messageID"),
element.getAttribute("message"))
.invokeSpecial();
} catch (Exception exception) {
logger.warning("error() raised " + exception.toString());
}
return null;
}
/**
* Handles a "setAI"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element setAI(Element element) {
Player p = (Player) getGame().getFreeColGameObject(element.getAttribute("player"));
p.setAI(Boolean.valueOf(element.getAttribute("ai")).booleanValue());
return null;
}
/**
* Handles an "chooseFoundingFather"-request.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element chooseFoundingFather(Element element) {
ChooseFoundingFatherMessage message
= new ChooseFoundingFatherMessage(getGame(), element);
List<FoundingFather> ffs = message.getFathers();
FoundingFather ff = gui.showChooseFoundingFatherDialog(ffs);
if (ff != null) {
message.setResult(ff);
getFreeColClient().getMyPlayer().setCurrentFather(ff);
}
return message.toXMLElement();
}
/**
* Handles a "diplomacy"-request. If the message informs of an
* acceptance or rejection then display the result and return
* null. If the message is a proposal, then ask the user about
* it and return the response with appropriate response set.
*
* @param element The element (root element in a DOM-parsed XML tree)
* containing a "diplomacy"-message.
* @return A diplomacy response, or null if none required.
*/
private Element diplomacy(Element element) {
Player player = getFreeColClient().getMyPlayer();
DiplomacyMessage message = new DiplomacyMessage(getGame(), element);
Unit unit = message.getUnit();
if (unit == null) {
logger.warning("Unit omitted from diplomacy message.");
return null;
}
Settlement settlement = message.getSettlement();
if (settlement == null) {
logger.warning("Settlement omitted from diplomacy message.");
return null;
}
Player other = (player.owns(unit)) ? settlement.getOwner()
: unit.getOwner();
String nation = Messages.message(other.getNationName());
DiplomaticTrade agreement = message.getAgreement();
switch (agreement.getStatus()) {
case ACCEPT_TRADE:
new ShowInformationMessageSwingTask(StringTemplate
.template("negotiationDialog.offerAccepted")
.addName("%nation%", nation)).show();
new UpdateMenuBarSwingTask().invokeLater();
break;
case REJECT_TRADE:
new ShowInformationMessageSwingTask(StringTemplate
.template("negotiationDialog.offerRejected")
.addName("%nation%", nation)).show();
new UpdateMenuBarSwingTask().invokeLater();
break;
case PROPOSE_TRADE:
DiplomaticTrade ourAgreement = gui.showNegotiationDialog(unit, settlement, agreement);
if (ourAgreement == null) {
agreement.setStatus(TradeStatus.REJECT_TRADE);
} else {
agreement = ourAgreement;
}
return new DiplomacyMessage(unit, settlement, agreement)
.toXMLElement();
default:
logger.warning("Bogus trade status: " + agreement.getStatus());
break;
}
return null;
}
/**
* Handles an "indianDemand"-request.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element indianDemand(Element element) {
Game game = getGame();
Player player = getFreeColClient().getMyPlayer();
IndianDemandMessage message = new IndianDemandMessage(game, element);
Unit unit = message.getUnit(game);
if (unit == null) {
logger.warning("IndianDemand with null unit: " + element.getAttribute("unit"));
return null;
}
Colony colony = message.getColony(game);
if (colony == null) {
logger.warning("IndianDemand with null colony: " + element.getAttribute("colony"));
return null;
} else if (!player.owns(colony)) {
throw new IllegalArgumentException("Demand to anothers colony");
}
Goods goods = message.getGoods();
String goldStr = Integer.toString(message.getGold());
boolean accepted;
ModelMessage m = null;
String nation = Messages.message(unit.getOwner().getNationName());
int opt = getFreeColClient().getClientOptions()
.getInteger(ClientOptions.INDIAN_DEMAND_RESPONSE);
if (goods == null) {
switch (opt) {
case ClientOptions.INDIAN_DEMAND_RESPONSE_ASK:
accepted = new ShowConfirmDialogSwingTask(colony.getTile(),
StringTemplate.template("indianDemand.gold.text")
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addAmount("%amount%", message.getGold()),
"indianDemand.gold.yes",
"indianDemand.gold.no").confirm();
break;
case ClientOptions.INDIAN_DEMAND_RESPONSE_ACCEPT:
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.gold.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", goldStr);
accepted = true;
break;
case ClientOptions.INDIAN_DEMAND_RESPONSE_REJECT:
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.gold.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", goldStr);
accepted = false;
break;
default:
throw new IllegalArgumentException("Impossible option value.");
}
} else {
String amount = String.valueOf(goods.getAmount());
switch (opt) {
case ClientOptions.INDIAN_DEMAND_RESPONSE_ASK:
if (goods.getType().isFoodType()) {
accepted = new ShowConfirmDialogSwingTask(colony.getTile(),
StringTemplate.template("indianDemand.food.text")
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addAmount("%amount%", goods.getAmount()),
"indianDemand.food.yes",
"indianDemand.food.no").confirm();
} else {
accepted = new ShowConfirmDialogSwingTask(colony.getTile(),
StringTemplate.template("indianDemand.other.text")
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addAmount("%amount%", goods.getAmount())
.add("%goods%", goods.getType().getNameKey()),
"indianDemand.other.yes",
"indianDemand.other.no").confirm();
}
break;
case ClientOptions.INDIAN_DEMAND_RESPONSE_ACCEPT:
if (goods.getType().isFoodType()) {
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.food.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", amount);
} else {
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.other.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", amount)
.add("%goods%", goods.getNameKey());
}
accepted = true;
break;
case ClientOptions.INDIAN_DEMAND_RESPONSE_REJECT:
if (goods.getType().isFoodType()) {
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.food.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", amount);
} else {
m = new ModelMessage(ModelMessage.MessageType.DEMANDS,
"indianDemand.other.text", colony, unit)
.addName("%nation%", nation)
.addName("%colony%", colony.getName())
.addName("%amount%", amount)
.add("%goods%", goods.getNameKey());
}
accepted = false;
break;
default:
throw new IllegalArgumentException("Impossible option value.");
}
}
if (m != null) {
player.addModelMessage(m);
getFreeColClient().getInGameController().nextModelMessage();
}
message.setResult(accepted);
return message.toXMLElement();
}
/**
* Handles a "spyResult" message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element spyResult(Element element) {
// The element contains two children, being the full and
// normal versions of the settlement-being-spied-upon's tile.
// It has to be the tile, as otherwise we do not see the units
// defending the settlement. So, we have to unpack, update
// with the first, display, then update with the second. This
// is hacky as the client could retain the settlement
// information, but the potential abuses are limited.
NodeList nodeList = element.getChildNodes();
if (nodeList.getLength() != 2) {
logger.warning("spyResult length = " + nodeList.getLength());
return null;
}
Game game = getGame();
final Element fullTile = (Element) nodeList.item(0);
final Element normalTile = (Element) nodeList.item(1);
String tileId = element.getAttribute("tile");
FreeColGameObject fcgo;
if (tileId == null
|| (fcgo = game.getFreeColGameObjectSafely(tileId)) == null
|| !(fcgo instanceof Tile)) {
logger.warning("spyResult bad tile = " + tileId);
return null;
}
final Tile tile = (Tile) fcgo;
tile.readFromXMLElement(fullTile);
Colony colony = tile.getColony();
if (colony == null) {
tile.readFromXMLElement(normalTile);
} else {
new SpyColonySwingTask(colony, normalTile).invokeLater();
}
return null;
}
/**
* Handles a "monarchAction"-request.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element monarchAction(Element element) {
Game game = getGame();
MonarchActionMessage message = new MonarchActionMessage(game, element);
MonarchAction action = message.getAction();
boolean accept = new ShowMonarchPanelSwingTask(action,
message.getTemplate()).confirm();
message.setResult(accept);
Element reply; // Some actions require an answer.
switch (action) {
case RAISE_TAX_ACT: case RAISE_TAX_WAR: case OFFER_MERCENARIES:
reply = message.toXMLElement();
break;
default:
reply = null;
break;
}
new UpdateMenuBarSwingTask().invokeLater();
return reply;
}
/**
* Handles a "setStance"-request.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element setStance(Element element) {
final FreeColClient freeColClient = getFreeColClient();
Player player = freeColClient.getMyPlayer();
Game game = getGame();
Stance stance = Enum.valueOf(Stance.class, element.getAttribute("stance"));
Player first = (Player) game.getFreeColGameObject(element.getAttribute("first"));
Player second = (Player) game.getFreeColGameObject(element.getAttribute("second"));
Stance old = first.getStance(second);
try {
first.setStance(second, stance);
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "Illegal stance transition", e);
return null;
}
logger.info("Stance transition: " + old.toString()
+ " -> " + stance.toString());
if (player == first && old == Stance.UNCONTACTED) {
gui.playSound("sound.event.meet."
+ second.getNationID());
}
return null;
}
/**
* Handles an "addPlayer"-message.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
private Element addPlayer(Element element) {
Element playerElement = (Element) element.getElementsByTagName(Player.getXMLElementTagName()).item(0);
if (getGame().getFreeColGameObject(playerElement.getAttribute(FreeColObject.ID_ATTRIBUTE)) == null) {
Player newPlayer = new Player(getGame(), playerElement);
getGame().addPlayer(newPlayer);
} else {
getGame().getFreeColGameObject(playerElement.getAttribute(FreeColObject.ID_ATTRIBUTE)).readFromXMLElement(playerElement);
}
return null;
}
/**
* Disposes of the <code>Unit</code>s which are the children of this
* Element.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element disposeUnits(Element element) {
Game game = getGame();
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
// Do not read the whole unit out of the element as we are
// only going to dispose of it, not forgetting that the
// server may have already done so and its view will only
// mislead us here in the client.
Element e = (Element) nodes.item(i);
FreeColGameObject fcgo = game.getFreeColGameObjectSafely(e.getAttribute(FreeColObject.ID_ATTRIBUTE));
if (fcgo instanceof Unit) {
((Unit) fcgo).dispose();
} else {
logger.warning("Object is not a unit: " + ((fcgo == null) ? "null" : fcgo.getId()));
}
}
return null;
}
/**
* Add the objects which are the children of this Element.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element addObject(Element element) {
final FreeColClient fcc = getFreeColClient();
Game game = getGame();
Specification spec = game.getSpecification();
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Element e = (Element) nodes.item(i);
String owner = e.getAttribute("owner");
Player player = null;
if (!(game.getFreeColGameObjectSafely(owner) instanceof Player)) {
logger.warning("addObject with broken owner: "
+ ((owner == null) ? "(null)" : owner));
continue;
}
player = (Player) game.getFreeColGameObjectSafely(owner);
String tag = e.getTagName();
if (tag == null) {
logger.warning("addObject null tag");
} else if (tag == FoundingFather.getXMLElementTagName()) {
String id = e.getAttribute("id");
FoundingFather father = spec.getFoundingFather(id);
if (father != null) player.addFather(father);
} else if (tag == HistoryEvent.getXMLElementTagName()) {
HistoryEvent h = new HistoryEvent();
h.readFromXMLElement(e);
player.getHistory().add(h);
} else if (tag == LastSale.getXMLElementTagName()) {
LastSale s = new LastSale();
s.readFromXMLElement(e);
player.saveSale(s);
} else if (tag == ModelMessage.getXMLElementTagName()) {
ModelMessage m = new ModelMessage();
m.readFromXMLElement(e);
player.addModelMessage(m);
} else if (tag == TradeRoute.getXMLElementTagName()) {
TradeRoute t = new TradeRoute(game, e);
player.getTradeRoutes().add(t);
} else {
logger.warning("addObject unrecognized: " + tag);
}
}
return null;
}
/**
* Ask the player to name the new land.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element newLandName(Element element) {
Game game = getGame();
NewLandNameMessage message = new NewLandNameMessage(game, element);
Unit unit = message.getUnit(game);
String defaultName = message.getNewLandName();
if (unit == null || defaultName == null
|| unit.getTile() == null) return null;
// Offer to name the land.
new NewLandNameSwingTask(unit, defaultName, message.getWelcomer(game),
message.getCamps()).invokeLater();
return null;
}
/**
* Ask the player to name a new region.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element newRegionName(Element element) {
Game game = getGame();
NewRegionNameMessage message = new NewRegionNameMessage(game, element);
String name = message.getNewRegionName();
Region region = message.getRegion(game);
Tile tile = message.getTile(game);
if (name == null || region == null) return null;
// Offer to name the region.
new NewRegionNameSwingTask(tile, region, message.getNewRegionName())
.invokeLater();
return null;
}
/**
* Ask the player to choose migrants from a fountain of youth event.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element fountainOfYouth(Element element) {
String migrants = element.getAttribute("migrants");
int n;
try {
n = Integer.parseInt(migrants);
} catch (NumberFormatException e) {
n = -1;
}
if (n > 0) {
// Without Brewster, the migrants have already been selected
// and were updated to the European docks by the server.
final int m = n;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < m; i++) {
int index = gui.showEmigrationPanel(true);
getFreeColClient().askServer().emigrate(index + 1);
}
}
});
}
return null;
}
/**
* Ask the player to choose something to loot.
*
* @param element The element (root element in a DOM-parsed XML tree) that
* holds all the information.
*/
public Element lootCargo(Element element) {
Game game = getGame();
LootCargoMessage message = new LootCargoMessage(game, element);
Unit unit = message.getUnit(game);
List<Goods> goods = message.getGoods();
if (unit == null || goods == null) return null;
new LootCargoSwingTask(unit, message.getDefenderId(), goods)
.invokeLater();
return null;
}
/**
* Trivial handler to allow the server to signal to the client
* that an offer that caused a popup (for example, a native demand
* or diplomacy proposal) has not been answered quickly enough and
* that the offering player has assumed this player has
* refused-by-inaction, and therefore, the popup needs to be
* closed.
*
* @return Null.
*/
public Element closeMenus() {
gui.closeMenus();
return null;
}
/**
* Handle all the children of this element.
*
* @param connection The <code>Connection</code> the element arrived on.
* @param element The <code>Element</code> to process.
* @return An <code>Element</code> containing the response/s.
*/
public Element multiple(Connection connection, Element element) {
NodeList nodes = element.getChildNodes();
List<Element> results = new ArrayList<Element>();
for (int i = 0; i < nodes.getLength(); i++) {
try {
Element reply = handle(connection, (Element) nodes.item(i));
if (reply != null) results.add(reply);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught crash in multiple item " + i
+ ", continuing.", e);
}
}
return DOMMessage.collapseElements(results);
}
/**
*
* Handler methods end here.
*
*/
/**
* This utility class is the base class for tasks that need to run in the
* event dispatch thread.
*/
abstract static class SwingTask implements Runnable {
private static final Logger taskLogger = Logger.getLogger(SwingTask.class.getName());
/**
* Run the task and wait for it to complete.
*
* @return return value from {@link #doWork()}.
* @throws InvocationTargetException on unexpected exceptions.
*/
public Object invokeAndWait() throws InvocationTargetException {
verifyNotStarted();
markStarted(true);
try {
SwingUtilities.invokeAndWait(this);
} catch (InterruptedException e) {
throw new InvocationTargetException(e);
}
return _result;
}
/**
* Run the task at some later time. Any exceptions will occur in the
* event dispatch thread. The return value will be set, but at present
* there is no good way to know if it is valid yet.
*/
public void invokeLater() {
verifyNotStarted();
markStarted(false);
SwingUtilities.invokeLater(this);
}
/*
* Some calls can be required from both within the EventDispatchThread
* (when the client controller calls InGameInputHandler.handle() with
* replies to its requests to the server), and from outside of the
* thread when handling other player moves. The former case must be done
* right now, the latter needs to be queued and waited for.
*/
public Object invokeSpecial() throws InvocationTargetException {
return (SwingUtilities.isEventDispatchThread())
? doWork()
: invokeAndWait();
}
/**
* Mark started and set the synchronous flag.
*
* @param synchronous The synch/asynch flag.
*/
private synchronized void markStarted(boolean synchronous) {
_synchronous = synchronous;
_started = true;
}
/**
* Mark finished.
*/
private synchronized void markDone() {
_started = false;
}
/**
* Throw an exception if the task is started.
*/
private synchronized void verifyNotStarted() {
if (_started) {
throw new IllegalStateException("Swing task already started!");
}
}
/**
* Check if the client is waiting.
*
* @return true if client is waiting for a result.
*/
private synchronized boolean isSynchronous() {
return _synchronous;
}
/**
* Run method, call {@link #doWork()} and save the return value. Also
* catch any exceptions. In synchronous mode they will be rethrown to
* the original thread, in asynchronous mode they will be logged and
* ignored. Nothing is gained by crashing the event dispatch thread.
*/
public final void run() {
try {
taskLogger.log(Level.FINEST, "Running Swing task "
+ getClass().getName() + "...");
setResult(doWork());
taskLogger.log(Level.FINEST, "Swing task "
+ getClass().getName()
+ " returned " + _result);
} catch (RuntimeException e) {
taskLogger.log(Level.WARNING, "Swing task "
+ getClass().getName() + " failed!", e);
// Let the exception bubble up if the calling thread is waiting
if (isSynchronous()) {
throw e;
}
} finally {
markDone();
}
}
/**
* Get the return vale from {@link #doWork()}.
*
* @return result.
*/
public synchronized Object getResult() {
return _result;
}
/**
* Save result.
*
* @param r The result.
*/
private synchronized void setResult(Object r) {
_result = r;
}
/**
* Override this method to do the actual work.
*
* @return result.
*/
protected abstract Object doWork();
private Object _result;
private boolean _synchronous;
private boolean _started;
}
/**
* Base class for Swing tasks that need to do a simple update
* without return value, and use the canvas.
*/
abstract class NoResultCanvasSwingTask extends SwingTask {
protected Object doWork() {
doNoResultWork();
return null;
}
abstract void doNoResultWork();
}
/**
* This task refreshes the entire canvas.
*/
class RefreshCanvasSwingTask extends NoResultCanvasSwingTask {
private final boolean requestFocus;
/**
* Default constructor, simply refresh canvas.
*/
public RefreshCanvasSwingTask() {
this(false);
}
/**
* Constructor.
*
* @param requestFocus True to request focus after refresh.
*/
public RefreshCanvasSwingTask(boolean requestFocus) {
this.requestFocus = requestFocus;
}
protected void doNoResultWork() {
gui.refresh();
if (requestFocus && !gui.isShowingSubPanel()) {
gui.requestFocusInWindow();
}
}
}
/**
* This class displays a dialog that lets the player choose goods to loot.
*/
class LootCargoSwingTask extends NoResultCanvasSwingTask {
private Unit unit;
private String defenderId;
private List<Goods> goods;
/**
* Constructor.
*
* @param goods A list of <code>Goods</code> to choose from.
*/
public LootCargoSwingTask(Unit unit, String defenderId,
List<Goods> goods) {
this.unit = unit;
this.defenderId = defenderId;
this.goods = goods;
}
protected void doNoResultWork() {
goods = gui.showCaptureGoodsDialog(unit, goods);
if (!goods.isEmpty()) {
getFreeColClient().askServer().loot(unit, defenderId, goods);
}
}
}
class NewLandNameSwingTask extends NoResultCanvasSwingTask {
private Unit unit;
private String defaultName;
private Player welcomer;
private String camps;
/**
* Constructor.
*
* @param unit The <code>Unit</code> that has come ashore.
* @param defaultName The default new land name.
* @param welcomer An optional <code>Player</code> that is welcoming
* this player to the new world.
* @param camps The number of camps of the welcomer.
*/
public NewLandNameSwingTask(Unit unit, String defaultName,
Player welcomer, String camps) {
this.unit = unit;
this.defaultName = defaultName;
this.welcomer = welcomer;
this.camps = camps;
}
protected void doNoResultWork() {
// Player names the land.
Tile tile = unit.getTile();
String name = gui.showInputDialog(tile,
StringTemplate.template("newLand.text"), defaultName,
"newLand.yes", null, true);
// Check if there is a welcoming native offering land.
boolean accept = false;
if (welcomer != null) {
String messageId = (welcomer.owns(tile))
? "welcomeOffer.text" : "welcomeSimple.text";
String type = ((IndianNationType) welcomer
.getNationType()).getSettlementTypeKey(true);
accept = gui.showConfirmDialog(tile,
StringTemplate.template(messageId)
.addStringTemplate("%nation%", welcomer.getNationName())
.addName("%camps%", camps)
.add("%settlementType%", type),
"welcome.yes", "welcome.no");
}
// Respond to the server.
FreeColClient fcc = getFreeColClient();
fcc.askServer().newLandName(unit, name, welcomer, accept);
// Add tutorial message.
Player player = unit.getOwner();
String key = FreeColActionUI.getHumanKeyStrokeText(fcc
.getActionManager().getFreeColAction("buildColonyAction")
.getAccelerator());
player.addModelMessage(new ModelMessage(ModelMessage.MessageType.TUTORIAL,
"tutorial.buildColony", player)
.addName("%build_colony_key%", key)
.add("%build_colony_menu_item%", "buildColonyAction.name")
.add("%orders_menu_item%", "menuBar.orders"));
fcc.getInGameController().nextModelMessage();
}
}
class NewRegionNameSwingTask extends NoResultCanvasSwingTask {
private Tile tile;
private Region region;
private String defaultName;
/**
* Constructor.
*
* @param tile The <code>Tile</code> where the region is discovered.
* @param region The <code>Region</code> that is discovered.
* @param defaultName The default name of the new region.
*/
public NewRegionNameSwingTask(Tile tile, Region region,
String defaultName) {
this.tile = tile;
this.region = region;
this.defaultName = defaultName;
}
protected void doNoResultWork() {
String name = gui.showInputDialog(tile,
StringTemplate.template("nameRegion.text")
.addName("%type%", Messages.message(region.getLabel())),
defaultName, "ok", null, false);
if (name == null || "".equals(name)) name = defaultName;
getFreeColClient().askServer().newRegionName(region, tile, name);
}
}
class RefreshTilesSwingTask extends NoResultCanvasSwingTask {
public RefreshTilesSwingTask(Tile oldTile, Tile newTile) {
super();
_oldTile = oldTile;
_newTile = newTile;
}
void doNoResultWork() {
gui.refreshTile(_oldTile);
gui.refreshTile(_newTile);
}
private final Tile _oldTile;
private final Tile _newTile;
}
/**
* This task plays an unit movement animation in the Canvas.
*/
class UnitMoveAnimationCanvasSwingTask extends NoResultCanvasSwingTask {
private final Unit unit;
private final Tile destinationTile;
private final Tile sourceTile;
private boolean focus;
/**
* Constructor.
* Play the unit movement animation, optionally focusing on
* the source tile.
*
* @param unit The <code>Unit</code> that is moving.
* @param sourceTile The <code>Tile</code> from which to move.
* @param destinationTile The <code>Tile</code> to move to.
* @param focus Focus on the source tile before the animation.
*/
public UnitMoveAnimationCanvasSwingTask(Unit unit, Tile sourceTile,
Tile destinationTile,
boolean focus) {
this.unit = unit;
this.sourceTile = sourceTile;
this.destinationTile = destinationTile;
this.focus = focus;
}
protected void doNoResultWork() {
if (focus || !gui.onScreen(sourceTile)) {
gui.setFocusImmediately(sourceTile);
}
Animations.unitMove(getFreeColClient(), gui, unit, sourceTile, destinationTile);
gui.refresh();
}
}
/**
* This task plays an unit attack animation in the Canvas.
*/
class UnitAttackAnimationCanvasSwingTask extends NoResultCanvasSwingTask {
private final Unit unit;
private final Unit defender;
private final boolean success;
private boolean focus;
/**
* Constructor - Play the unit attack animation, always focusing on the
* source tile.
*
* @param unit The <code>Unit</code> that is attacking.
* @param defender The <code>Unit</code> that is defending.
* @param success Did the attack succeed?
*/
public UnitAttackAnimationCanvasSwingTask(Unit unit, Unit defender,
boolean success) {
this(unit, defender, success, true);
}
/**
* Constructor - Play the unit attack animation, optionally focusing on
* the source tile.
*
* @param unit The <code>Unit</code> that is attacking.
* @param defender The <code>Unit</code> that is defending.
* @param success Did the attack succeed?
* @param focus Focus on the source tile before the animation.
*/
public UnitAttackAnimationCanvasSwingTask(Unit unit, Unit defender,
boolean success, boolean focus) {
this.unit = unit;
this.defender = defender;
this.success = success;
this.focus = focus;
}
protected void doNoResultWork() {
if (focus || !gui.onScreen(unit.getTile())) {
gui.setFocusImmediately(unit.getTile());
}
Animations.unitAttack(getFreeColClient(), gui, unit, defender, success);
gui.refresh();
}
}
/**
* This task shows an enhanced colony panel, then restores the
* normal information when it closes.
*/
class SpyColonySwingTask extends NoResultCanvasSwingTask {
private Colony colony;
private Element normalTile;
public SpyColonySwingTask(Colony colony, Element normalTile) {
this.colony = colony;
this.normalTile = normalTile;
}
protected void doNoResultWork() {
final Tile tile = colony.getTile();
gui.getCanvas().showColonyPanel(colony, new Runnable() {
public void run() {
tile.readFromXMLElement(normalTile);
}
});
}
}
/**
* This task reconnects to the server.
*/
class ReconnectSwingTask extends SwingTask {
protected Object doWork() {
getFreeColClient().getConnectController().reconnect();
return null;
}
}
/**
* This task updates the menu bar.
*/
class UpdateMenuBarSwingTask extends NoResultCanvasSwingTask {
protected void doNoResultWork() {
gui.updateMenuBar();
}
}
/**
* This task shows the victory panel.
*/
class ShowVictoryPanelSwingTask extends NoResultCanvasSwingTask {
protected void doNoResultWork() {
gui.showVictoryPanel();
}
}
/**
* This class shows a dialog and saves the answer (ok/cancel).
*/
class ShowConfirmDialogSwingTask extends SwingTask {
private Tile tile;
private StringTemplate text;
private String okText;
private String cancelText;
/**
* Constructor.
*
* @param tile An optional tile to make visible.
* @param text The key for the question.
* @param okText The key for the OK button.
* @param cancelText The key for the Cancel button.
*/
public ShowConfirmDialogSwingTask(Tile tile, StringTemplate text,
String okText, String cancelText) {
this.tile = tile;
this.text = text;
this.okText = okText;
this.cancelText = cancelText;
}
/**
* Show dialog and wait for selection.
*
* @return true if OK, false if Cancel.
*/
public boolean confirm() {
try {
Object result = invokeSpecial();
return ((Boolean) result).booleanValue();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
protected Object doWork() {
boolean choice = gui.showConfirmDialog(tile, text, okText,
cancelText);
return Boolean.valueOf(choice);
}
}
/**
* This class shows a an input dialog and saves the answer (ok/cancel).
*/
class ShowInputDialogSwingTask extends SwingTask {
private Tile tile;
private StringTemplate text;
private String defaultValue;
private String okText;
private String cancelText;
private boolean rejectEmpty;
/**
* Constructor.
*
* @param tile An optional tile to make visible.
* @param text A <code>StringTemplate</code> for the question.
* @param defaultValue The default value.
* @param okText The key for the OK button.
* @param cancelText The key for the Cancel button.
* @param rejectEmpty Reject the empty response.
*/
public ShowInputDialogSwingTask(Tile tile, StringTemplate text,
String defaultValue,
String okText, String cancelText,
boolean rejectEmpty) {
this.tile = tile;
this.text = text;
this.defaultValue = defaultValue;
this.okText = okText;
this.cancelText = cancelText;
this.rejectEmpty = rejectEmpty;
}
/**
* Show dialog and wait for selection.
*
* @return The result string.
*/
public String show() {
try {
Object result = invokeSpecial();
return (result instanceof String) ? (String) result : null;
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
protected Object doWork() {
String choice = gui.showInputDialog(tile, text, defaultValue,
okText, cancelText, rejectEmpty);
return choice;
}
}
/**
* Base class for dialog SwingTasks.
*/
abstract class ShowMessageSwingTask extends SwingTask {
/**
* Show dialog and wait for the user to dismiss it.
*/
public void show() {
try {
invokeSpecial();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
}
/**
* This class shows a model message.
*/
class ShowModelMessageSwingTask extends ShowMessageSwingTask {
/**
* Constructor.
*
* @param modelMessage The model message to show.
*/
public ShowModelMessageSwingTask(ModelMessage modelMessage) {
_modelMessage = modelMessage;
}
protected Object doWork() {
gui.showModelMessages(_modelMessage);
return null;
}
private ModelMessage _modelMessage;
}
/**
* This class shows an informational dialog.
*/
class ShowInformationMessageSwingTask extends ShowMessageSwingTask {
private StringTemplate message;
/**
* Constructor.
*
* @param message the StringTemplate
*/
public ShowInformationMessageSwingTask(StringTemplate message) {
this.message = message;
}
protected Object doWork() {
gui.showInformationMessage(null, message);
return null;
}
}
/**
* This class shows an error dialog.
*/
class ShowErrorMessageSwingTask extends ShowMessageSwingTask {
/**
* Constructor.
*
* @param messageId The i18n-keyname of the error message to display.
* @param message An alternative message to display if the resource
* specified by <code>messageID</code> is unavailable.
*/
public ShowErrorMessageSwingTask(String messageId, String message) {
_messageId = messageId;
_message = message;
}
protected Object doWork() {
gui.errorMessage(_messageId, _message);
return null;
}
private String _messageId;
private String _message;
}
/**
* This class displays a dialog that lets the player pick a Founding Father.
*/
abstract class ShowSelectSwingTask extends SwingTask {
/**
* Show dialog and wait for selection.
*
* @return selection.
*/
public int select() {
try {
Object result = invokeAndWait();
return ((Integer) result).intValue();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
}
/**
* This class shows the monarch panel.
*/
class ShowMonarchPanelSwingTask extends SwingTask {
private MonarchAction action;
private StringTemplate replace;
/**
* Constructor.
*
* @param action The action key.
* @param replace The replacement values.
*/
public ShowMonarchPanelSwingTask(MonarchAction action,
StringTemplate replace) {
this.action = action;
this.replace = replace;
}
/**
* Show dialog and wait for selection.
*
* @return true if OK, false if Cancel.
*/
public boolean confirm() {
try {
Object result = invokeAndWait();
return ((Boolean) result).booleanValue();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
protected Object doWork() {
boolean choice = gui.showMonarchPanelDialog(action, replace);
return Boolean.valueOf(choice);
}
}
}