freecol/src/net/sf/freecol/client/gui/dialog/NegotiationDialog.java

1325 lines
49 KiB
Java

/**
* Copyright (C) 2002-2022 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.gui.dialog;
import static net.sf.freecol.common.util.CollectionUtils.alwaysTrue;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.ListCellRenderer;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import net.miginfocom.swing.MigLayout;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.ChoiceItem;
import net.sf.freecol.client.gui.FontLibrary;
import net.sf.freecol.client.gui.panel.MigPanel;
import net.sf.freecol.client.gui.panel.Utility;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTradeItem;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.DiplomaticTrade.TradeContext;
import net.sf.freecol.common.model.DiplomaticTrade.TradeStatus;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GoldTradeItem;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsContainer;
import net.sf.freecol.common.model.GoodsLocation;
import net.sf.freecol.common.model.GoodsTradeItem;
import net.sf.freecol.common.model.InciteTradeItem;
import net.sf.freecol.common.model.Market;
import net.sf.freecol.common.model.NationSummary;
import net.sf.freecol.common.model.Ownable;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.Stance;
import net.sf.freecol.common.model.StanceTradeItem;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.TradeItem;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitTradeItem;
/**
* The panel that allows negotiations between players.
* <p>
* Panel Layout:
* <p>
* The Negotiation Dialog is laid out in three columns. The outer two columns are 30% of the panel's width while the
* center column is 40% the panel's width.
* <p style="display: block; font-family: monospace; white-space: pre; margin: 1em 0;">
* --------------------------------------------------------------------------
* | negotiationDialog.title |
* | ---------------------------------------------------------------------- |
* | Left Pane | Center Pane | Right Pane |
* | | (centerPanel) | |
* | ---------------------------------------------------------------------- |
* | [cancelButton][sendButton] |
* --------------------------------------------------------------------------
* <p>
* The Left and Right Panes are laid out as follows:
* <p style="display: block; font-family: monospace; white-space: pre; margin: 1em 0;">
* | --------------- |
* | gold |
* | --------------- |
* | colony |
* | --------------- |
* | stance |
* | --------------- |
* | war declaration |
* | --------------- |
* TODO: Improve layout description
*/
public final class NegotiationDialog extends FreeColDialog<DiplomaticTrade> {
private static final Logger logger = Logger.getLogger(NegotiationDialog.class.getName());
private static final int HUGE_DEMAND = 100000;
private static final String ADD = "add";
private static final String CLEAR = "clear";
private class RemoveAction extends AbstractAction {
private final TradeItem item;
public RemoveAction(TradeItem item) {
this.item = item;
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
agreement.remove(item);
updateDialog(true);
}
}
private class ColonyTradeItemPanel extends MigPanel
implements ActionListener {
private final Player source;
private final JComboBox<Colony> colonyBox;
private final JButton clearButton;
private final JButton addButton;
private final JLabel label;
private final List<Colony> allColonies;
/**
* Creates a new {@code ColonyTradeItemPanel} instance.
*
* @param source The {@code Player} source.
*/
public ColonyTradeItemPanel(Player source) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.colonyBox = new JComboBox<>();
this.clearButton = Utility.localizedButton("negotiationDialog.clear");
this.clearButton.addActionListener(this);
this.clearButton.setActionCommand(CLEAR);
this.addButton = Utility.localizedButton("negotiationDialog.add");
this.addButton.addActionListener(this);
this.addButton.setActionCommand(ADD);
this.label = Utility.localizedLabel(Messages.getName("model.tradeItem.colony"));
this.allColonies = source.getColonyList();
setBorder(Utility.getSimpleLineBorder());
add(this.label);
add(this.colonyBox);
add(this.clearButton, "split 2");
add(this.addButton);
setSize(getPreferredSize());
}
/**
* Update this panel.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
private void update(DiplomaticTrade dt) {
if (!source.isEuropean()) return;
// Remove all action listeners, so the update has no effect (except
// updating the list).
ActionListener[] listeners = this.colonyBox.getActionListeners();
for (ActionListener al : listeners) {
this.colonyBox.removeActionListener(al);
}
List<Colony> available = new ArrayList<>(allColonies);
for (Colony c : dt.getColoniesGivenBy(source)) {
if (available.contains(c)) {
available.remove(c);
} else {
allColonies.add(c); // did not know about this!
}
}
this.colonyBox.removeAllItems();
for (Colony c : available) this.colonyBox.addItem(c);
boolean enable = !available.isEmpty();
this.clearButton.setEnabled(!enable);
this.addButton.setEnabled(enable);
this.colonyBox.setEnabled(enable);
this.label.setEnabled(enable);
for (ActionListener al : listeners) {
this.colonyBox.addActionListener(al);
}
}
// Implement ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
NegotiationDialog.this.addColonyTradeItem(source,
(Colony)colonyBox.getSelectedItem());
break;
case CLEAR:
NegotiationDialog.this
.removeTradeItems(ColonyTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
private class GoldTradeItemPanel extends MigPanel
implements ActionListener {
private final Player source;
private final JSpinner spinner;
/**
* Creates a new {@code GoldTradeItemPanel} instance.
*
* @param source The {@code Player} that is trading.
* @param gold The maximum amount of gold to trade.
*/
public GoldTradeItemPanel(Player source, int gold) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.spinner = new JSpinner(new SpinnerNumberModel(0, 0, gold, 1));
JButton clearButton = Utility.localizedButton("negotiationDialog.clear");
clearButton.addActionListener(this);
clearButton.setActionCommand(CLEAR);
JButton addButton = Utility.localizedButton("negotiationDialog.add");
addButton.addActionListener(this);
addButton.setActionCommand(ADD);
// adjust entry size
((JSpinner.DefaultEditor)this.spinner.getEditor())
.getTextField()
.setColumns(5);
setBorder(Utility.getSimpleLineBorder());
add(Utility.localizedLabel(Messages.getName("model.tradeItem.gold")));
add(Utility.localizedLabel(StringTemplate
.template("negotiationDialog.goldAvailable")
.addAmount("%amount%", gold)));
add(this.spinner);
add(clearButton, "split 2");
add(addButton);
setSize(getPreferredSize());
}
/**
* Update this panel.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
public void update(DiplomaticTrade dt) {
int gold = dt.getGoldGivenBy(source);
if (gold >= 0) {
SpinnerNumberModel model
= (SpinnerNumberModel)spinner.getModel();
model.setValue(gold);
}
}
// Implement ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
int amount = ((Integer)spinner.getValue());
NegotiationDialog.this.addGoldTradeItem(source, amount);
break;
case CLEAR:
NegotiationDialog.this.removeTradeItems(GoldTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
private class GoodsTradeItemPanel extends MigPanel
implements ActionListener {
private class GoodsBoxRenderer extends JLabel
implements ListCellRenderer<Goods> {
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends Goods> list,
Goods value, int index, boolean isSelected,
boolean cellHasFocus) {
setText((value == null) ? ""
: Messages.message(value.getLabel(true)));
return this;
}
}
private final Player source;
private final JComboBox<Goods> goodsBox;
private final JButton clearButton;
private final JButton addButton;
private final JLabel label;
private final List<Goods> allGoods;
/**
* Creates a new {@code GoodsTradeItemPanel} instance.
*
* @param source The {@code Player} nominally in possession of the
* goods (this may be totally fictional).
* @param allGoods The {@code Goods} to trade.
*/
public GoodsTradeItemPanel(Player source, List<Goods> allGoods) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.goodsBox = new JComboBox<>(new DefaultComboBoxModel<>());
this.goodsBox.setRenderer(new GoodsBoxRenderer());
this.clearButton = Utility.localizedButton("negotiationDialog.clear");
this.clearButton.addActionListener(this);
this.clearButton.setActionCommand(CLEAR);
this.addButton = Utility.localizedButton("negotiationDialog.add");
this.addButton.addActionListener(this);
this.addButton.setActionCommand(ADD);
this.label = Utility.localizedLabel(Messages.nameKey("model.tradeItem.goods"));
this.allGoods = allGoods;
setBorder(Utility.getSimpleLineBorder());
add(this.label);
add(this.goodsBox);
add(this.clearButton, "split 2");
add(this.addButton);
setSize(getPreferredSize());
}
/**
* Update this panel.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
public void update(DiplomaticTrade dt) {
// Remove all action listeners, so the update has no
// effect (except updating the list).
ActionListener[] listeners = this.goodsBox.getActionListeners();
for (ActionListener al : listeners) {
this.goodsBox.removeActionListener(al);
}
List<Goods> available = new ArrayList<>(allGoods);
for (Goods goods : dt.getGoodsGivenBy(source)) {
// Remove the ones already on the table
for (int i = 0; i < available.size(); i++) {
Goods g = available.get(i);
if (g.getType() == goods.getType()) {
if (g.getAmount() <= goods.getAmount()) {
available.remove(i);
} else {
g.setAmount(g.getAmount() - goods.getAmount());
}
break;
}
}
}
this.goodsBox.removeAllItems();
for (Goods g : available) goodsBox.addItem(g);
boolean enable = !available.isEmpty();
this.label.setEnabled(enable);
this.clearButton.setEnabled(!enable);
this.addButton.setEnabled(enable);
this.goodsBox.setEnabled(enable);
for (ActionListener al : listeners) {
this.goodsBox.addActionListener(al);
}
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
NegotiationDialog.this.addGoodsTradeItem(source,
(Goods)goodsBox.getSelectedItem());
break;
case CLEAR:
NegotiationDialog.this
.removeTradeItems(GoodsTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
private class InciteTradeItemPanel extends MigPanel
implements ActionListener {
private class InciteBoxRenderer extends JLabel
implements ListCellRenderer<Player> {
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends Player> list,
Player value, int index, boolean isSelected,
boolean cellHasFocus) {
setText((value == null) ? ""
: Messages.message(value.getNationLabel()));
return this;
}
}
private final Player source;
private final Player other;
private final JComboBox<Player> victimBox;
private final JLabel label;
private final JButton clearButton;
private final JButton addButton;
private final List<Player> available = new ArrayList<>();
/**
* Creates a new {@code InciteTradeItemPanel} instance.
*
* @param source The {@code Player} that is trading.
* @param other The {@code Player} negotiated with.
*/
public InciteTradeItemPanel(Player source, Player other) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.other = other;
this.victimBox = new JComboBox<>(new DefaultComboBoxModel<>());
this.victimBox.setRenderer(new InciteBoxRenderer());
this.clearButton = Utility.localizedButton("negotiationDialog.clear");
this.clearButton.addActionListener(this);
this.clearButton.setActionCommand(CLEAR);
this.addButton = Utility.localizedButton("negotiationDialog.add");
this.addButton.addActionListener(this);
this.addButton.setActionCommand(ADD);
this.label = Utility.localizedLabel(Messages.nameKey("model.tradeItem.incite"));
setBorder(Utility.getSimpleLineBorder());
available.clear();
final Predicate<Player> incitablePred = p ->
p != this.other && this.source.getStance(p).isIncitable();
available.addAll(transform(getGame().getLivePlayers(this.source),
incitablePred));
add(this.label);
add(this.victimBox);
add(this.clearButton, "split 2");
add(this.addButton);
setSize(getPreferredSize());
}
/**
* Update this panel.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
public void update(DiplomaticTrade dt) {
// Remove all action listeners, so the update has no
// effect (except updating the list).
ActionListener[] listeners = this.victimBox.getActionListeners();
for (ActionListener al : listeners) {
this.victimBox.removeActionListener(al);
}
this.victimBox.removeAllItems();
for (Player p : available) victimBox.addItem(p);
boolean enable = !available.isEmpty();
this.label.setEnabled(enable);
this.clearButton.setEnabled(!enable);
this.addButton.setEnabled(enable);
this.victimBox.setEnabled(enable);
for (ActionListener al : listeners) {
this.victimBox.addActionListener(al);
}
}
// Implement ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
Player victim = (Player)victimBox.getSelectedItem();
if (victim != null) {
NegotiationDialog.this
.addInciteTradeItem(source, victim);
} break;
case CLEAR:
NegotiationDialog.this
.removeTradeItems(InciteTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
/**
* Class for the stance trade panel. Access needs to be public so
* that comboBoxLabel() is externally visible.
*/
public class StanceTradeItemPanel extends MigPanel
implements ActionListener {
private class StanceBoxRenderer extends JLabel
implements ListCellRenderer<Stance> {
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends Stance> list,
Stance value, int index, boolean isSelected,
boolean cellHasFocus) {
setText((value == null) ? "" : Messages.getName(value));
return this;
}
}
private final Player source;
private final Player target;
private final JComboBox<Stance> stanceBox;
private final JButton clearButton;
private final JButton addButton;
/**
* Creates a new {@code StanceTradeItemPanel} instance.
*
* @param source The {@code Player} offering the stance change.
* @param target The {@code Player} to consider the stance change.
*/
public StanceTradeItemPanel(Player source, Player target) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.target = target;
this.stanceBox = new JComboBox<>(new DefaultComboBoxModel<>());
this.stanceBox.setRenderer(new StanceBoxRenderer());
this.clearButton = Utility.localizedButton("negotiationDialog.clear");
this.clearButton.addActionListener(this);
this.clearButton.setActionCommand(CLEAR);
this.addButton = Utility.localizedButton("negotiationDialog.add");
this.addButton.addActionListener(this);
this.addButton.setActionCommand(ADD);
setBorder(Utility.getSimpleLineBorder());
add(Utility.localizedLabel(Messages.nameKey("model.tradeItem.stance")));
add(this.stanceBox);
add(this.clearButton, "split 2");
add(this.addButton);
}
/**
* Select the item with a given stance.
*
* @param stance The {@code Stance} to select.
*/
private void setSelectedValue(Stance stance) {
for (int i = 0; i < stanceBox.getItemCount(); i++) {
if (stanceBox.getItemAt(i) == stance) {
stanceBox.setSelectedItem(i);
}
}
}
/**
* Update this panel with a given trade.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
public void update(DiplomaticTrade dt) {
stanceBox.removeAllItems();
Stance stance = source.getStance(target);
if (stance != Stance.WAR) {
stanceBox.addItem(Stance.WAR);
} else {
stanceBox.addItem(Stance.CEASE_FIRE);
}
if (stance != Stance.PEACE && stance != Stance.UNCONTACTED) {
stanceBox.addItem(Stance.PEACE);
}
if (stance == Stance.PEACE) {
stanceBox.addItem(Stance.ALLIANCE);
}
Stance select = dt.getStance();
if (select != null) setSelectedValue(select);
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
Stance stance = (Stance)stanceBox.getSelectedItem();
NegotiationDialog.this.addStanceTradeItem(stance);
break;
case CLEAR:
NegotiationDialog.this
.removeTradeItems(StanceTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
private class UnitTradeItemPanel extends MigPanel
implements ActionListener {
private class UnitBoxRenderer extends JLabel
implements ListCellRenderer<Unit> {
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends Unit> list,
Unit value, int index, boolean isSelected,
boolean cellHasFocus) {
setText((value == null) ? "" : value.getDescription());
return this;
}
}
private final Player source;
private final JComboBox<Unit> unitBox;
private final JButton clearButton;
private final JButton addButton;
private final JLabel label;
private final List<Unit> allUnits;
/**
* Creates a new {@code UnitTradeItemPanel} instance.
*
* @param source The {@code Player} nominally in posession of the
* unit (this may be totally fictional).
* @param allUnits The {@code Unit}s to trade.
*/
public UnitTradeItemPanel(Player source, List<Unit> allUnits) {
super(new MigLayout("wrap 1", "", ""));
this.source = source;
this.unitBox = new JComboBox<>(new DefaultComboBoxModel<>());
this.unitBox.setRenderer(new UnitBoxRenderer());
this.clearButton = Utility.localizedButton("negotiationDialog.clear");
this.clearButton.addActionListener(this);
this.clearButton.setActionCommand(CLEAR);
this.addButton = Utility.localizedButton("negotiationDialog.add");
this.addButton.addActionListener(this);
this.addButton.setActionCommand(ADD);
this.label = Utility.localizedLabel(Messages.nameKey("model.tradeItem.unit"));
this.allUnits = allUnits;
setBorder(Utility.getSimpleLineBorder());
add(this.label);
add(this.unitBox);
add(this.clearButton, "split 2");
add(this.addButton);
setSize(getPreferredSize());
}
/**
* Update this panel with a given trade.
*
* @param dt The {@code DiplomaticTrade} to update with.
*/
private void update(DiplomaticTrade dt) {
// Remove all action listeners, so the update has no
// effect (except updating the list).
ActionListener[] listeners = unitBox.getActionListeners();
for (ActionListener al : listeners) {
unitBox.removeActionListener(al);
}
List<Unit> available = new ArrayList<>(allUnits);
for (Unit u : dt.getUnitsGivenBy(source)) {
// Remove the ones already on the table
if (available.contains(u)) {
available.remove(u);
} else {
allUnits.add(u); // Did not know about this!
}
}
unitBox.removeAllItems();
for (Unit u : available) unitBox.addItem(u);
boolean enable = !available.isEmpty();
this.label.setEnabled(enable);
clearButton.setEnabled(!enable);
addButton.setEnabled(enable);
unitBox.setEnabled(enable);
for (ActionListener al : listeners) {
unitBox.addActionListener(al);
}
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final String command = ae.getActionCommand();
if (null != command) switch (command) {
case ADD:
NegotiationDialog.this.addUnitTradeItem(source,
(Unit)unitBox.getSelectedItem());
break;
case CLEAR:
NegotiationDialog.this
.removeTradeItems(UnitTradeItem.class);
break;
default:
logger.warning("Bad command: " + command);
break;
}
}
}
/** The other player in the negotiation (!= getMyPlayer()). */
private final Player otherPlayer;
/** The agreement under negotiation. */
private final DiplomaticTrade agreement;
/** A comment message. */
private final StringTemplate comment;
/** The panels for various negotiable data. */
private StanceTradeItemPanel stancePanel;
private GoldTradeItemPanel goldOfferPanel, goldDemandPanel;
private ColonyTradeItemPanel colonyOfferPanel, colonyDemandPanel;
private GoodsTradeItemPanel goodsOfferPanel, goodsDemandPanel;
private InciteTradeItemPanel inciteOfferPanel, inciteDemandPanel;
private UnitTradeItemPanel unitOfferPanel, unitDemandPanel;
/** A panel showing a summary of the current agreement. */
private JPanel summary;
/** Useful internal messages. */
private StringTemplate demand, offer;
private String exchangeMessage;
/** Responses. */
private ChoiceItem<DiplomaticTrade> send = null, accept = null;
/**
* Creates a new {@code NegotiationDialog} instance.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param frame The owner frame.
* @param our Our {@code FreeColGameObject} that is negotiating.
* @param other The other {@code FreeColGameObject}.
* @param agreement The {@code DiplomaticTrade} agreement that
* is being negotiated.
* @param comment An optional {@code StringTemplate}
* commentary message.
*/
public NegotiationDialog(FreeColClient freeColClient, JFrame frame,
FreeColGameObject our, FreeColGameObject other,
DiplomaticTrade agreement, StringTemplate comment) {
super(freeColClient, frame);
final Player player = getMyPlayer();
final Unit ourUnit = (our instanceof Unit) ? (Unit)our : null;
final Colony ourColony = (our instanceof Colony) ? (Colony)our : null;
final Unit otherUnit = (other instanceof Unit) ? (Unit)other : null;
final Colony otherColony = (other instanceof Colony) ? (Colony)other
: null;
this.otherPlayer = ((Ownable)other).getOwner();
this.agreement = agreement;
this.comment = comment;
StringTemplate nation = player.getCountryLabel(),
otherNation = otherPlayer.getCountryLabel();
this.demand = StringTemplate.template("negotiationDialog.demand")
.addStringTemplate("%nation%", nation)
.addStringTemplate("%otherNation%", otherNation);
this.offer = StringTemplate.template("negotiationDialog.offer")
.addStringTemplate("%nation%", nation)
.addStringTemplate("%otherNation%", otherNation);
this.exchangeMessage = Messages.message("negotiationDialog.exchange");
NationSummary ns = igc().nationSummary(otherPlayer);
int gold = (ns == null
|| ns.getGold() == Player.GOLD_NOT_ACCOUNTED) ? HUGE_DEMAND
: ns.getGold();
this.goldDemandPanel = new GoldTradeItemPanel(otherPlayer, gold);
gold = (player.getGold() == Player.GOLD_NOT_ACCOUNTED) ? HUGE_DEMAND
: player.getGold();
this.goldOfferPanel = new GoldTradeItemPanel(player, gold);
StringTemplate tutorial = null;
TradeContext context = agreement.getContext();
switch (context) {
case CONTACT:
if (freeColClient.tutorialMode()) {
tutorial = StringTemplate.key("negotiationDialog.contact.tutorial");
}
this.stancePanel = new StanceTradeItemPanel(player, otherPlayer);
this.inciteOfferPanel = new InciteTradeItemPanel(player, otherPlayer);
this.inciteDemandPanel = new InciteTradeItemPanel(otherPlayer, player);
break;
case DIPLOMATIC:
this.stancePanel = new StanceTradeItemPanel(player, otherPlayer);
this.colonyDemandPanel = new ColonyTradeItemPanel(otherPlayer);
this.colonyOfferPanel = new ColonyTradeItemPanel(player);
this.goodsDemandPanel = this.goodsOfferPanel = null;
this.inciteOfferPanel = new InciteTradeItemPanel(player, otherPlayer);
this.inciteDemandPanel = new InciteTradeItemPanel(otherPlayer, player);
this.unitOfferPanel = this.unitDemandPanel = null;
break;
case TRADE:
this.stancePanel = null;
this.colonyDemandPanel = this.colonyOfferPanel = null;
List<Goods> goods = getAnyGoods();
this.goodsDemandPanel = new GoodsTradeItemPanel(otherPlayer, goods);
GoodsLocation gl = (ourUnit != null) ? ourUnit : ourColony;
goods = (ourUnit != null) ? ourUnit.getGoodsList()
: ourColony.getCompactGoodsList();
for (Goods g : goods) {
if (g.getAmount() > GoodsContainer.CARGO_SIZE) {
g.setAmount(GoodsContainer.CARGO_SIZE);
}
g.setLocation(gl);
}
this.goodsOfferPanel = new GoodsTradeItemPanel(player, goods);
this.inciteOfferPanel = this.inciteDemandPanel = null;
this.unitDemandPanel = new UnitTradeItemPanel(otherPlayer,
getUnitUnitList(null));
this.unitOfferPanel = new UnitTradeItemPanel(player,
((ourUnit != null) ? getUnitUnitList(ourUnit)
: ourColony.getUnitList()));
break;
case TRIBUTE:
this.stancePanel = new StanceTradeItemPanel(player, otherPlayer);
this.colonyDemandPanel = this.colonyOfferPanel = null;
this.goodsDemandPanel = this.goodsOfferPanel = null;
this.inciteOfferPanel = new InciteTradeItemPanel(player, otherPlayer);
this.inciteDemandPanel = new InciteTradeItemPanel(otherPlayer, player);
this.unitOfferPanel = this.unitDemandPanel = null;
break;
default:
throw new IllegalStateException("Bogus trade context: " + context);
}
this.summary = new MigPanel(new MigLayout("wrap 2", "[20px:n:n][]"));
this.summary.setOpaque(false);
this.summary.add(Utility.localizedTextArea(comment), "center, span 2");
/**
* Build Layout of Diplomatic Trade Dialog
*/
JPanel panel = new MigPanel(new MigLayout("wrap 3",
"[30%|40%|30%]", ""));
// Main Panel Header
panel.add(Utility.localizedHeader("negotiationDialog.title."
+ agreement.getContext().getKey(),
Utility.FONTSPEC_TITLE),
"span 3, center");
// Panel contents Header row
JTextArea labelDemandMessage = Utility.localizedTextArea(this.demand);
Font font = FontLibrary.getScaledFont("normal-bold-tiny");
labelDemandMessage.setFont(font);
panel.add(labelDemandMessage);
JTextArea blank = new JTextArea(" ");
blank.setVisible(false);
panel.add(blank, "");
JTextArea labelOfferMessage = Utility.localizedTextArea(this.offer);
labelOfferMessage.setFont(font);
panel.add(labelOfferMessage);
// Panel contents
// TODO: Expand center panel so that contents fill cell horizontally.
panel.add(this.goldDemandPanel); // Left pane
JPanel centerPanel = new MigPanel(new MigLayout("wrap 1"));
centerPanel.setMinimumSize(new Dimension(250, 50));
if (tutorial != null) {
// Display only if tutorial variable contents overriden
// Can only occur if: First Contact with a forgeign Nation
JTextArea tutArea = Utility.localizedTextArea(tutorial, 30);
centerPanel.add(tutArea, "center");
}
JScrollPane scroll = new JScrollPane(this.summary,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.getViewport().setOpaque(false);
scroll.setBorder(null);
centerPanel.add(scroll, "top, width 100%");
panel.add(centerPanel, "spany, top"); // Center pane
panel.add(this.goldOfferPanel); // Right pane
if (this.colonyDemandPanel != null) {
panel.add(this.colonyDemandPanel);
panel.add(this.colonyOfferPanel);
}
if (this.stancePanel != null) {
panel.add(this.stancePanel, "skip");
}
if (this.goodsDemandPanel != null) {
panel.add(this.goodsDemandPanel);
panel.add(this.goodsOfferPanel);
}
if (this.inciteDemandPanel != null) {
panel.add(this.inciteDemandPanel);
panel.add(this.inciteOfferPanel);
}
if (this.unitDemandPanel != null) {
panel.add(this.unitDemandPanel);
panel.add(this.unitOfferPanel);
}
if (FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.MENUS)) {
panel.add(new JLabel("Version = " + agreement.getVersion()));
}
String str;
List<ChoiceItem<DiplomaticTrade>> c = choices();
if (agreement.getVersion() > 0) { // A new offer can not be accepted
str = Messages.message("negotiationDialog.accept");
c.add(this.accept = new ChoiceItem<>(str, (DiplomaticTrade)null));
}
str = Messages.message("negotiationDialog.send");
c.add(this.send = new ChoiceItem<>(str,
(DiplomaticTrade)null).okOption());
if (agreement.getVersion() > 0 || context != TradeContext.CONTACT) {
str = Messages.message("negotiationDialog.cancel");
c.add(new ChoiceItem<>(str,
(DiplomaticTrade)null).cancelOption().defaultOption());
}
updateDialog(false);
ImageIcon icon = new ImageIcon((otherColony != null)
? getImageLibrary().getScaledSettlementImage(otherColony)
: getImageLibrary().getScaledUnitImage(otherUnit));
initializeDialog(frame, DialogType.QUESTION, true, panel, icon, c);
}
/**
* {@inheritDoc}
*/
@Override
public DiplomaticTrade getResponse() {
Object value = getValue();
TradeStatus s = (value == null) ? TradeStatus.REJECT_TRADE
: (value == this.accept) ? TradeStatus.ACCEPT_TRADE
: (value == this.send) ? TradeStatus.PROPOSE_TRADE
: TradeStatus.REJECT_TRADE;
agreement.setStatus(s);
return agreement;
}
/**
* Gets a list of all possible storable goods (one cargo load).
* Note that these goods are fictional. They are the goods that
* *might* be in the other player's store. Therefore they have a
* bogus location (i.e. not the actual goods container).
*
* @return A list of storable {@code Goods}.
*/
private List<Goods> getAnyGoods() {
final Game game = getGame();
final Specification spec = getSpecification();
return transform(spec.getStorableGoodsTypeList(), alwaysTrue(),
gt -> new Goods(game, null, gt, GoodsContainer.CARGO_SIZE));
}
/**
* Get a list of units to offer that are associated with a given unit.
*
* @param unit The {@code Unit} that is trading.
* @return A list of {@code Unit}s.
*/
private List<Unit> getUnitUnitList(Unit unit) {
List<Unit> ul = new ArrayList<>();
if (unit != null) {
if (unit.isCarrier()) {
ul.addAll(unit.getUnitList());
} else if (unit.isOnCarrier()) {
ul.addAll(unit.getCarrier().getUnitList());
} else {
ul.add(unit);
}
}
return ul;
}
/**
* Update the entire dialog.
*
* @param changed If true, the agreement has changed, and the accept button
* needs to be disabled
*/
private void updateDialog(boolean changed) {
if (this.goldOfferPanel != null) {
this.goldOfferPanel.update(agreement);
}
if (this.stancePanel != null) {
this.stancePanel.update(agreement);
}
if (this.colonyOfferPanel != null) {
this.colonyOfferPanel.update(agreement);
}
if (this.colonyDemandPanel != null) {
this.colonyDemandPanel.update(agreement);
}
if (this.goodsOfferPanel != null) {
this.goodsOfferPanel.update(agreement);
}
if (this.goodsDemandPanel != null) {
this.goodsDemandPanel.update(agreement);
}
if (this.inciteOfferPanel != null) {
this.inciteOfferPanel.update(agreement);
}
if (this.inciteDemandPanel != null) {
this.inciteDemandPanel.update(agreement);
}
if (this.unitOfferPanel != null) {
this.unitOfferPanel.update(agreement);
}
if (this.unitDemandPanel != null) {
this.unitDemandPanel.update(agreement);
}
if (changed && this.accept != null) {
this.accept.setEnabled(false);
}
updateSummary();
}
/**
* Gets a trade item button for a given item.
*
* @param item The {@code TradeItem} to make a button for.
* @param saleDir Boolean to indicate the EU price for sale (T) or buy (F)
* @return A new {@code JButton} for the item.
*/
private JButton getTradeItemButton(TradeItem item, boolean saleDir) {
Market market = getMyPlayer().getMarket();
JButton button = new JButton(new RemoveAction(item));
// Checks if the items are goods
if (item.getGoods() != null) {
int buyPriceTotal = market.getBidPrice(item.getGoods().getType(), item.getGoods().getAmount());
int salePriceTotal = market.getSalePrice(item.getGoods().getType(), item.getGoods().getAmount());
// Depending on saleDir, creates a button for goods w/ EU buy or sale price
if (saleDir) {
button.setText(Messages.message(item.getLabel()) + " " +
Messages.message(StringTemplate
.template("negotiationDialog.euSalePrice")
.addAmount("%priceTotal%", salePriceTotal)));
} else {
button.setText(Messages.message(item.getLabel()) + " " +
Messages.message(StringTemplate
.template("negotiationDialog.euBuyPrice")
.addAmount("%priceTotal%", buyPriceTotal)));
}
} else {
// If not goods, follow protocol
button.setText(Messages.message(item.getLabel()));
}
button.setMargin(Utility.EMPTY_MARGIN);
button.setOpaque(false);
button.setForeground(Utility.getLinkColor());
button.setBorder(Utility.blankBorder(0, 0, 0, 0));
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
/**
* Update the text summary of the proposed transaction.
*/
private void updateSummary() {
final Player player = getMyPlayer();
summary.removeAll();
summary.add(Utility.localizedTextArea(comment), "center, span 2");
List<TradeItem> offers = agreement.getItemsGivenBy(player);
if (!offers.isEmpty()) {
summary.add(Utility.localizedLabel(this.offer), "span");
for (TradeItem item : offers) {
summary.add(getTradeItemButton(item, true), "skip");
}
}
List<TradeItem> demands = agreement.getItemsGivenBy(otherPlayer);
if (!demands.isEmpty()) {
if (offers.isEmpty()) {
summary.add(Utility.localizedLabel(this.demand), "span");
} else {
summary.add(new JLabel(exchangeMessage), "newline 20, span");
}
for (TradeItem item : demands) {
summary.add(getTradeItemButton(item, false), "skip");
}
}
}
/**
* Remove trade items of a given type.
*
* @param itemClass The class of {@code TradeItem} to remove.
*/
public void removeTradeItems(Class<? extends TradeItem> itemClass) {
this.agreement.removeType(itemClass);
updateDialog(true);
}
/**
* Adds a {@code ColonyTradeItem} to the list of TradeItems.
*
* @param source The sourced {@code Player}.
* @param colony The {@code Colony} to add.
*/
public void addColonyTradeItem(Player source, Colony colony) {
final Player player = getMyPlayer();
Player destination = (source == otherPlayer) ? player : otherPlayer;
agreement.add(new ColonyTradeItem(getGame(), source, destination, colony));
updateDialog(true);
}
/**
* Adds a {@code GoldTradeItem} to the list of TradeItems.
*
* @param source The source {@code Player}.
* @param amount The amount of gold.
*/
public void addGoldTradeItem(Player source, int amount) {
final Player player = getMyPlayer();
Player destination = (source == otherPlayer) ? player : otherPlayer;
agreement.add(new GoldTradeItem(getGame(), source, destination, amount));
updateDialog(true);
}
/**
* Adds a {@code GoodsTradeItem} to the list of TradeItems.
*
* @param source The source {@code Player}.
* @param goods The {@code Goods} to add.
*/
public void addGoodsTradeItem(Player source, Goods goods) {
final Player player = getMyPlayer();
Player destination = (source == otherPlayer) ? player : otherPlayer;
agreement.add(new GoodsTradeItem(getGame(), source, destination, goods));
updateDialog(true);
}
/**
* Add an {@code InciteTradeItem} to the list of trade items.
*
* @param source The source {@code Player}.
* @param victim The {@code Player} to be attacked.
*/
public void addInciteTradeItem(Player source, Player victim) {
final Player player = getMyPlayer();
Player destination = (source == otherPlayer) ? player : otherPlayer;
agreement.add(new InciteTradeItem(getGame(), source, destination, victim));
updateDialog(true);
}
/**
* Trade a stance change between the players.
*
* @param stance The {@code Stance} to trade.
*/
public void addStanceTradeItem(Stance stance) {
final Player player = getMyPlayer();
agreement.add(new StanceTradeItem(getGame(), otherPlayer, player, stance));
updateDialog(true);
}
/**
* Adds a {@code UnitTradeItem} to the list of TradeItems.
*
* @param source The source {@code Player}.
* @param unit The {@code Unit} to add.
*/
public void addUnitTradeItem(Player source, Unit unit) {
final Player player = getMyPlayer();
Player destination = (source == otherPlayer) ? player : otherPlayer;
agreement.add(new UnitTradeItem(getGame(), source, destination, unit));
updateDialog(true);
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void removeNotify() {
super.removeNotify();
removeAll();
this.stancePanel = null;
this.goldOfferPanel = this.goldDemandPanel = null;
this.colonyOfferPanel = this.colonyDemandPanel = null;
this.goodsOfferPanel = this.goodsDemandPanel = null;
this.inciteOfferPanel = this.inciteDemandPanel = null;
this.unitOfferPanel = this.unitDemandPanel = null;
this.summary = null;
this.demand = this.offer = null;
this.exchangeMessage = null;
}
}