/**
* 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.gui.panel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.freecolandroid.repackaged.java.awt.Color;
import org.freecolandroid.repackaged.java.awt.Font;
import org.freecolandroid.repackaged.java.awt.GridLayout;
import org.freecolandroid.repackaged.java.awt.event.ActionEvent;
import org.freecolandroid.repackaged.java.awt.event.ActionListener;
import org.freecolandroid.repackaged.javax.swing.ImageIcon;
import org.freecolandroid.repackaged.javax.swing.JButton;
import org.freecolandroid.repackaged.javax.swing.JLabel;
import org.freecolandroid.repackaged.javax.swing.JPanel;
import org.freecolandroid.repackaged.javax.swing.JSeparator;
import org.freecolandroid.repackaged.javax.swing.SwingConstants;
import net.miginfocom.swing.MigLayout;
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.ImageLibrary;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.common.model.AbstractGoods;
import net.sf.freecol.common.model.BuildableType;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.ExportData;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.FreeColObject;
import net.sf.freecol.common.model.GoodsContainer;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Market;
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.Unit;
import net.sf.freecol.common.model.Unit.Role;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.common.resources.ResourceManager;
/**
* This panel displays the Colony Report.
*/
public final class ReportColonyPanel extends ReportPanel
implements ActionListener {
private static final Comparator<GoodsType> goodsComparator
= new Comparator<GoodsType>() {
private int rank(GoodsType g) {
return (!g.isStorable() || g.isTradeGoods()) ? -1
: (g.isFoodType()) ? 1
: (g.isNewWorldGoodsType()) ? 2
: (g.isFarmed()) ? 3
: (g.isRawMaterial()) ? 4
: (g.isNewWorldLuxuryType()) ? 5
: (g.isRefined()) ? 6
: -1;
}
public int compare(GoodsType g1, GoodsType g2) {
int r1 = rank(g1);
int r2 = rank(g2);
return (r1 != r2) ? r1 - r2
: g1.getNameKey().compareTo(g2.getNameKey());
}
};
private static final Comparator<AbstractGoods> abstractGoodsComparator
= new Comparator<AbstractGoods>() {
public int compare(AbstractGoods a1, AbstractGoods a2) {
int cmp = a2.getAmount() - a1.getAmount();
return (cmp != 0) ? cmp
: goodsComparator.compare(a2.getType(), a1.getType());
}
};
private static final Comparator<Unit> teacherComparator
= new Comparator<Unit>() {
public int compare(Unit u1, Unit u2) {
int l1 = u1.getNeededTurnsOfTraining() - u1.getTurnsOfTraining();
int l2 = u2.getNeededTurnsOfTraining() - u2.getTurnsOfTraining();
int cmp = l1 - l2;
return (cmp != 0) ? cmp
: u2.getType().getId().compareTo(u1.getType().getId());
}
};
private static final String BUILDQUEUE = "buildQueue.";
private boolean useCompact = false;
private List<Colony> colonies;
private List<GoodsType> goodsTypes;
// Customized colours.
private Color cAlarm;
private Color cWarn;
private Color cPlain;
private Color cExport;
private Color cGood;
/**
* The constructor that will add the items to this panel.
* @param freeColClient
* @param gui
*
* @param parent The parent of this panel.
*/
public ReportColonyPanel(FreeColClient freeColClient, GUI gui) {
super(freeColClient, gui, Messages.message("reportColonyAction.name"));
colonies = getSortedColonies();
try {
useCompact = getClientOptions().getInteger(ClientOptions.COLONY_REPORT)
== ClientOptions.COLONY_REPORT_COMPACT;
} catch (Exception e) {
useCompact = false;
}
if (useCompact) {
initializeCompactColonyPanel();
updateCompactColonyPanel();
} else {
classicColonyPanel(colonies);
}
}
// Standard pretty version
private void classicColonyPanel(List<Colony> colonies) {
final int COLONISTS_PER_ROW = 20;
final int UNITS_PER_ROW = 14;
final int GOODS_PER_ROW = 10;
final int BUILDINGS_PER_ROW = 8;
// Display Panel
reportPanel.setLayout(new MigLayout("fill"));
for (Colony colony : colonies) {
// Name
JButton button = getLinkButton(colony.getName(), null,
colony.getId());
button.addActionListener(this);
reportPanel.add(button, "newline 20, split 2");
reportPanel.add(new JSeparator(JSeparator.HORIZONTAL), "growx");
// Currently building
BuildableType currentType = colony.getCurrentlyBuilding();
JLabel buildableLabel = null;
if (currentType != null) {
buildableLabel = new JLabel(new ImageIcon(ResourceManager.getImage(currentType.getId()
+ ".image", 0.66)));
buildableLabel.setToolTipText(Messages.message(StringTemplate.template("colonyPanel.currentlyBuilding")
.add("%buildable%", currentType.getNameKey())));
buildableLabel.setIcon(buildableLabel.getDisabledIcon());
}
// Units
JPanel colonistsPanel = new JPanel(new GridLayout(0, COLONISTS_PER_ROW));
colonistsPanel.setOpaque(false);
List<Unit> unitList = colony.getUnitList();
Collections.sort(unitList, getUnitTypeComparator());
for (Unit unit : unitList) {
UnitLabel unitLabel = new UnitLabel(getFreeColClient(), unit, getGUI(), true, true);
colonistsPanel.add(unitLabel);
}
JPanel unitsPanel = new JPanel(new GridLayout(0, UNITS_PER_ROW));
unitsPanel.setOpaque(false);
unitList = colony.getTile().getUnitList();
Collections.sort(unitList, getUnitTypeComparator());
for (Unit unit : unitList) {
UnitLabel unitLabel = new UnitLabel(getFreeColClient(), unit, getGUI(), true, true);
unitsPanel.add(unitLabel);
}
if(buildableLabel != null && currentType.getSpecification().getUnitTypeList().contains(currentType)) {
unitsPanel.add(buildableLabel);
}
reportPanel.add(colonistsPanel, "newline, growx");
reportPanel.add(unitsPanel, "newline, growx");
// Production
GoodsType horses = getSpecification().getGoodsType("model.goods.horses");
int count = 0;
for (GoodsType goodsType : getSpecification().getGoodsTypeList()) {
int newValue = colony.getNetProductionOf(goodsType);
int stockValue = colony.getGoodsCount(goodsType);
if (newValue != 0 || stockValue > 0) {
int maxProduction = 0;
for (Building building : colony.getBuildingsForProducing(goodsType)) {
maxProduction += building.getMaximumProduction();
}
ProductionLabel productionLabel = new ProductionLabel(getFreeColClient(), getGUI(), goodsType, newValue);
if (maxProduction > 0) {
productionLabel.setMaximumProduction(maxProduction);
}
if (goodsType == horses) {
// horse images don't stack well
productionLabel.setMaxGoodsIcons(1);
}
// Show stored items in ReportColonyPanel
productionLabel.setStockNumber(stockValue);
if (count % GOODS_PER_ROW == 0) {
reportPanel.add(productionLabel, "newline, split " + GOODS_PER_ROW);
} else {
reportPanel.add(productionLabel);
}
count++;
}
}
// Buildings
JPanel buildingsPanel = new JPanel(new GridLayout(0, BUILDINGS_PER_ROW));
buildingsPanel.setOpaque(false);
List<Building> buildingList = colony.getBuildings();
Collections.sort(buildingList);
for (Building building : buildingList) {
if(building.getType().isAutomaticBuild()) {
continue;
}
JLabel buildingLabel =
new JLabel(new ImageIcon(ResourceManager.getImage(building.getType().getId()
+ ".image", 0.66)));
buildingLabel.setToolTipText(Messages.message(building.getNameKey()));
buildingsPanel.add(buildingLabel);
}
if(buildableLabel != null && currentType.getSpecification().getBuildingTypeList().contains(currentType)) {
buildingsPanel.add(buildableLabel);
}
reportPanel.add(buildingsPanel, "newline, growx");
}
}
// Compact version
private void initializeCompactColonyPanel() {
Specification spec = getSpecification();
goodsTypes = new ArrayList<GoodsType>(spec.getGoodsTypeList());
Collections.sort(goodsTypes, goodsComparator);
while (!goodsTypes.get(0).isStorable()
|| goodsTypes.get(0).isTradeGoods()) {
goodsTypes.remove(0);
}
// Define the layout, with a column for each goods type.
String cols = "[l][c][c][c]";
for (int i = 0; i < goodsTypes.size(); i++) cols += "[c]";
cols += "[c][l][l][c][l]";
reportPanel.setLayout(new MigLayout("fillx, insets 0, gap 0 0",
cols, ""));
// Load the customized colours, with simple fallbacks.
cAlarm = ResourceManager.getColor("report.colony.alarmColor");
cWarn = ResourceManager.getColor("report.colony.warningColor");
cPlain = ResourceManager.getColor("report.colony.plainColor");
cExport = ResourceManager.getColor("report.colony.exportColor");
cGood = ResourceManager.getColor("report.colony.goodColor");
if (cAlarm == null) cAlarm = Color.RED;
if (cWarn == null) cWarn = Color.MAGENTA;
if (cPlain == null) cPlain = Color.DARK_GRAY;
if (cExport == null) cExport = Color.GREEN;
if (cGood == null) cGood = Color.BLUE;
}
/**
* Implement the action listener, checking for BUILDQUEUE events,
* generally displaying the colony panel if given a colony id, but
* otherwise delegating to the ReportPanel handler.
*
* @param event The incoming event.
*/
public void actionPerformed(ActionEvent event) {
if (useCompact) {
String command = event.getActionCommand();
if (command.startsWith(BUILDQUEUE)) {
command = command.substring(BUILDQUEUE.length());
FreeColGameObject fcgo
= getGame().getFreeColGameObject(command);
if (fcgo instanceof Colony) {
getGUI().showBuildQueuePanel((Colony) fcgo, new Runnable() {
public void run() {
updateCompactColonyPanel();
}
});
return;
}
} else {
FreeColGameObject fcgo
= getGame().getFreeColGameObject(command);
if (fcgo instanceof Colony) {
getGUI().showColonyPanel((Colony) fcgo, new Runnable() {
public void run() {
updateCompactColonyPanel();
}
});
return;
}
}
}
super.actionPerformed(event);
}
// Work done by (optional) oldType would be better done by newType
// because it could produce amount more goodsType.
private class Suggestion {
public UnitType oldType;
public UnitType newType;
public GoodsType goodsType;
public int amount;
public Suggestion(UnitType oldType, UnitType newType,
GoodsType goodsType, int amount) {
this.oldType = oldType;
this.newType = newType;
this.goodsType = goodsType;
this.amount = amount;
}
};
private void updateCompactColonyPanel() {
reportPanel.removeAll();
Market market = getMyPlayer().getMarket();
conciseHeaders(goodsTypes, true, market);
for (Colony colony : colonies) {
// Do not include colonies that have been abandoned but are
// still on the colonies list.
if (colony.getUnitCount() > 0) {
updateColony(colony);
}
}
conciseHeaders(goodsTypes, false, market);
}
private void updateColony(Colony colony) {
final Specification spec = getSpecification();
final GoodsType foodType = spec.getPrimaryFoodType();
final UnitType colonistType = spec.getDefaultUnitType();
final ImageLibrary lib = getGUI().getImageLibrary();
// Assemble the fundamental facts about this colony
final String cac = colony.getId();
List<Tile> exploreTiles = new ArrayList<Tile>();
List<Tile> clearTiles = new ArrayList<Tile>();
List<Tile> plowTiles = new ArrayList<Tile>();
List<Tile> roadTiles = new ArrayList<Tile>();
colony.getColonyTileTodo(exploreTiles, clearTiles, plowTiles,
roadTiles);
boolean plowMe = plowTiles.size() > 0
&& plowTiles.get(0) == colony.getTile();
int newColonist;
boolean famine;
if (colony.getGoodsCount(foodType) > Settlement.FOOD_PER_COLONIST) {
famine = false;
newColonist = 1;
} else {
int newFood = colony.getAdjustedNetProductionOf(foodType);
famine = newFood < 0
&& (colony.getGoodsCount(foodType) / -newFood) <= 3;
newColonist = (newFood == 0) ? 0
: (newFood < 0) ? colony.getGoodsCount(foodType) / newFood - 1
: (Settlement.FOOD_PER_COLONIST
- colony.getGoodsCount(foodType)) / newFood + 1;
}
int grow = colony.getPreferredSizeChange();
int bonus = colony.getProductionBonus();
// Field: A button for the colony.
// Colour: bonus in {-2,2} => {alarm, warn, plain, export, good}
// Font: Bold if famine is threatening.
JButton b = colourButton(cac, colony.getName(), null,
(bonus <= -2) ? cAlarm
: (bonus == -1) ? cWarn
: (bonus == 0) ? cPlain
: (bonus == 1) ? cExport
: cGood,
null);
if (famine) {
b.setFont(b.getFont().deriveFont(Font.BOLD));
}
reportPanel.add(b, "newline");
// Field: The number of potential colony tiles that need
// exploring.
// Colour: Always cAlarm
if (exploreTiles.size() > 0) {
b = colourButton(cac, Integer.toString(exploreTiles.size()),
null, cAlarm,
stpl("report.colony.exploring.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", exploreTiles.size()));
reportPanel.add(b);
} else {
reportPanel.add(new JLabel(""));
}
// Field: The number of existing colony tiles that would
// benefit from ploughing.
// Colour: Always cAlarm
// Font: Bold if one of the tiles is the colony center.
if (plowTiles.size() > 0) {
b = colourButton(cac, Integer.toString(plowTiles.size()),
null, cAlarm,
stpl("report.colony.plowing.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", plowTiles.size()));
if (plowMe) {
b.setFont(b.getFont().deriveFont(Font.BOLD));
}
reportPanel.add(b);
} else {
reportPanel.add(new JLabel(""));
}
// Field: The number of existing colony tiles that would
// benefit from a road.
// Colour: cAlarm
if (roadTiles.size() > 0) {
b = colourButton(cac, Integer.toString(roadTiles.size()),
null, cAlarm,
stpl("report.colony.roadBuilding.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", roadTiles.size()));
reportPanel.add(b);
} else {
reportPanel.add(new JLabel(""));
}
// Fields: The net production of each storable+non-trade-goods
// goods type.
// Colour: cAlarm if too low, cWarn if negative, empty if no
// production, cPlain if production balanced at zero,
// otherwise must be positive, wherein cExport
// if exported, cAlarm if too high, else cGood.
final int adjustment = colony.getWarehouseCapacity()
/ GoodsContainer.CARGO_SIZE;
for (GoodsType g : goodsTypes) {
int p = colony.getAdjustedNetProductionOf(g);
ExportData exportData = colony.getExportData(g);
int low = exportData.getLowLevel() * adjustment;
int high = exportData.getHighLevel() * adjustment;
int amount = colony.getGoodsCount(g);
Color c;
StringTemplate tip;
if (p < 0) {
if (amount < low) {
int turns = -amount / p + 1;
c = cAlarm;
tip = stpl("report.colony.production.low.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p)
.addAmount("%turns%", turns);
} else {
c = cWarn;
tip = stpl("report.colony.production.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p);
}
} else if (p == 0) {
if (colony.getProductionOf(g) == 0) {
c = null;
tip = null;
} else {
c = cPlain;
tip = stpl("report.colony.production.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p);
}
} else if (exportData.isExported()) {
c = cExport;
tip = stpl("report.colony.production.export.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p)
.addAmount("%export%", exportData.getExportLevel());
} else if (g != foodType
&& amount + p > colony.getWarehouseCapacity()) {
c = cAlarm;
int waste = amount + p - colony.getWarehouseCapacity();
tip = stpl("report.colony.production.waste.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p)
.addAmount("%waste%", waste);
} else if (g != foodType && amount > high) {
int turns = (colony.getWarehouseCapacity() - amount) / p;
c = cWarn;
tip = stpl("report.colony.production.high.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p)
.addAmount("%turns%", turns);
} else {
c = cGood;
tip = stpl("report.colony.production.description")
.addName("%colony%", colony.getName())
.add("%goods%", g.getNameKey())
.addAmount("%amount%", p);
}
if (c == null) reportPanel.add(new JLabel(""));
else {
b = colourButton(cac, Integer.toString(p), null, c, tip);
reportPanel.add(b);
}
}
// Collect the types of the units at work in the colony
// (colony tiles and buildings) that are suboptimal (and
// are not just temporarily there because they are being
// taught), the types for sites that really need a new
// unit, the teachers, and the units that are not working.
// TODO: this needs to be merged with the requirements
// checking code, but that in turn should be opened up
// so the AI can use it...
HashMap<UnitType, Suggestion> improve
= new HashMap<UnitType, Suggestion>();
HashMap<UnitType, Suggestion> want
= new HashMap<UnitType, Suggestion>();
List<Unit> teachers = new ArrayList<Unit>();
List<Unit> notWorking = new ArrayList<Unit>();
for (Unit u : colony.getTile().getUnitList()) {
if (u.getState() != Unit.UnitState.FORTIFIED
&& u.getState() != Unit.UnitState.SENTRY) {
notWorking.add(u);
}
}
for (WorkLocation wl : colony.getAvailableWorkLocations()) {
if (!wl.canBeWorked()) {
continue;
} else if (wl.canTeach()) {
teachers.addAll(wl.getUnitList());
continue;
}
UnitType expert;
GoodsType work;
boolean needsWorker = !wl.isFull();
int delta;
// Check first if the units are working, and then add a
// suggestion if there is a better type of unit for the
// work being done.
for (Unit u : wl.getUnitList()) {
if (u.getTeacher() != null) {
continue; // Ignore students, they are temporary
} else if ((work = u.getWorkType()) == null) {
notWorking.add(u);
needsWorker = true;
} else if ((expert = spec.getExpertForProducing(work)) != null
&& expert != u.getType()
&& (delta = wl.getPotentialProduction(expert, work)
- wl.getPotentialProduction(u.getType(), work)) > 0
&& wantGoods(wl, work, u, expert)) {
addSuggestion(improve, u.getType(), expert,
work, delta);
}
}
// Add a suggestion for an extra worker if there is
// space, valid work to do, an expert type to do it,
// and the goods are wanted.
if (needsWorker
&& (work = bestProduction(wl, colonistType)) != null
&& (expert = spec.getExpertForProducing(work)) != null
&& (delta = wl.getPotentialProduction(expert, work)) > 0
&& wantGoods(wl, work, null, expert)) {
addSuggestion(want, null, expert, work, delta);
}
}
// Make a list of unit types that are not working at their
// speciality, including the units just standing around.
List<UnitType> couldWork = new ArrayList<UnitType>();
for (Unit u : notWorking) {
GoodsType t = u.getWorkType();
WorkLocation wl = (u.getLocation() instanceof WorkLocation)
? (WorkLocation) u.getLocation()
: null;
GoodsType w = bestProduction(wl, colonistType);
if (w == null || w != t) couldWork.add(u.getType());
}
// Field: New colonist arrival or famine warning.
// Colour: cGood if arriving eventually, blank if not enough food
// to grow, cWarn if negative, cAlarm if famine soon.
if (newColonist > 0) {
b = colourButton(cac, Integer.toString(newColonist),
null, cGood,
stpl("report.colony.arriving.description")
.addName("%colony%", colony.getName())
.add("%unit%", colonistType.getNameKey())
.addAmount("%turns%", newColonist));
reportPanel.add(b);
} else if (newColonist < 0) {
b = colourButton(cac, Integer.toString(-newColonist),
null, (newColonist >= -3) ? cAlarm : cWarn,
stpl("report.colony.starving.description")
.addName("%colony%", colony.getName())
.addAmount("%turns%", -newColonist));
reportPanel.add(b);
} else {
reportPanel.add(new JLabel(""));
}
// Field: What is currently being built (clickable if on the
// buildqueue) and the turns until it completes, including
// units being taught.
// Colour: cAlarm bold "Nothing" if nothing being built, cAlarm
// with no turns if no production, cGood with turns if
// completing, cAlarm with turns if will block, turns
// indicates when blocking occurs.
BuildableType build = colony.getCurrentlyBuilding();
int fields = 1 + teachers.size();
String layout = (fields > 1) ? "split " + fields : null;
String qac = BUILDQUEUE + colony.getId();
if (build == null) {
b = colourButton(qac, Messages.message("nothing"),
null, cAlarm,
stpl("report.colony.making.noconstruction.description")
.addName("%colony%", colony.getName()));
b.setFont(b.getFont().deriveFont(Font.BOLD));
} else {
AbstractGoods needed = new AbstractGoods();
int turns = colony.getTurnsToComplete(build, needed);
String name = Messages.message(build.getNameKey());
if (turns == FreeColObject.UNDEFINED) {
b = colourButton(qac, name, null, cAlarm,
stpl("report.colony.making.noconstruction.description")
.addName("%colony%", colony.getName()));
} else if (turns >= 0) {
turns++;
name += " " + Integer.toString(turns);
b = colourButton(qac, name, null, cGood,
stpl("report.colony.making.constructing.description")
.addName("%colony%", colony.getName())
.add("%buildable%", build.getNameKey())
.addAmount("%turns%", turns));;
} else if (turns < 0) {
GoodsType goodsType = needed.getType();
int goodsAmount = needed.getAmount()
- colony.getGoodsCount(goodsType);
turns = -turns;
name += " " + Integer.toString(turns);
b = colourButton(qac, name, null, cAlarm,
stpl("report.colony.making.blocking.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", goodsAmount)
.add("%goods%", goodsType.getNameKey())
.add("%buildable%", build.getNameKey())
.addAmount("%turns%", turns));
}
}
reportPanel.add(b, layout);
layout = null;
Collections.sort(teachers, teacherComparator);
for (Unit u : teachers) {
int left = u.getNeededTurnsOfTraining()
- u.getTurnsOfTraining();
if (left <= 0) {
b = colourButton(cac, Integer.toString(0),
lib.getUnitImageIcon(u.getType(), Role.DEFAULT,
true, 0.333), cAlarm,
stpl("report.colony.making.noteach.description")
.addName("%colony%", colony.getName())
.addStringTemplate("%teacher%", u.getLabel()));
} else {
b = colourButton(cac, Integer.toString(left),
lib.getUnitImageIcon(u.getType(), Role.DEFAULT,
true, 0.333), Color.BLACK,
stpl("report.colony.making.educating.description")
.addName("%colony%", colony.getName())
.addStringTemplate("%teacher%", u.getLabel())
.addAmount("%turns%", left));
}
reportPanel.add(b);
}
if (fields <= 0) reportPanel.add(new JLabel(""));
// Field: The units that could be upgraded.
if (!improve.isEmpty()) {
addUnits(improve, couldWork, colony, grow);
} else {
reportPanel.add(new JLabel(""));
}
// Field: The number of colonists that can be added to a
// colony without damaging the production bonus, unless
// the colony is inefficient in which case add the number
// of colonists to remove to fix the inefficiency.
// Colour: Blue if efficient/Red if inefficient.
if (grow < 0) {
b = colourButton(cac, Integer.toString(-grow), null, cAlarm,
stpl("report.colony.shrinking.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", -grow));
reportPanel.add(b);
} else if (grow > 0) {
b = colourButton(cac, Integer.toString(grow), null, cGood,
stpl("report.colony.growing.description")
.addName("%colony%", colony.getName())
.addAmount("%amount%", grow));
reportPanel.add(b);
} else {
reportPanel.add(new JLabel(""));
}
// Field: The units the colony could make good use of.
if (!want.isEmpty()) {
// TODO: explain food limitations better
grow = Math.min(grow, colony.getNetProductionOf(foodType)
/ Settlement.FOOD_PER_COLONIST);
addUnits(want, couldWork, colony, grow);
} else {
reportPanel.add(new JLabel(""));
}
}
private StringTemplate stpl(String messageId) {
return StringTemplate.template(messageId);
}
private void conciseHeaders(List<GoodsType> goodsTypes, boolean top,
Market market) {
reportPanel.add(new JSeparator(JSeparator.HORIZONTAL),
"newline, span, growx");
reportPanel.add(newLabel("report.colony.name.header", null, null,
stpl("report.colony.name.description")),
"newline");
reportPanel.add(newLabel("report.colony.explore.header", null, null,
stpl("report.colony.explore.description")));
reportPanel.add(newLabel("report.colony.plow.header", null, null,
stpl("report.colony.plow.description")));
reportPanel.add(newLabel("report.colony.road.header", null, null,
stpl("report.colony.road.description")));
for (GoodsType g : goodsTypes) {
ImageIcon ii = getGUI().getImageLibrary()
.getScaledGoodsImageIcon(g, 0.667);
JLabel l = newLabel(null, ii, null,
stpl("report.colony.production.header")
.add("%goods%", g.getNameKey()));
l.setEnabled(market == null || market.getArrears(g) <= 0);
reportPanel.add(l);
}
final UnitType colonistType = getSpecification().getDefaultUnitType();
ImageIcon colonistIcon
= getGUI().getImageLibrary().getUnitImageIcon(colonistType,
Role.DEFAULT, true, 0.333);
reportPanel.add(newLabel(null, colonistIcon, null,
stpl("report.colony.birth.description")));
reportPanel.add(newLabel("report.colony.making.header", null, null,
stpl("report.colony.making.description")));
reportPanel.add(newLabel("report.colony.improve.header", null, null,
stpl("report.colony.improve.description")));
reportPanel.add(newLabel("report.colony.grow.header", null, null,
stpl("report.colony.grow.description")));
reportPanel.add(newLabel("report.colony.wanted.header", null, null,
stpl("report.colony.wanted.description")));
reportPanel.add(new JSeparator(JSeparator.HORIZONTAL),
"newline, span, growx");
}
private JLabel newLabel(String h, ImageIcon i, Color c, StringTemplate t) {
if (h != null) h = Messages.message(h);
JLabel l = new JLabel(h, i, SwingConstants.CENTER);
l.setForeground((c == null) ? Color.BLACK : c);
if (t != null) {
l.setToolTipText(Messages.message(t));
}
return l;
}
private JButton colourButton(String action, String h,
ImageIcon i, Color c, StringTemplate t) {
if (h != null) {
if (Messages.containsKey(h)) h = Messages.message(h);
}
JButton b = getLinkButton(h, i, action);
b.setForeground((c == null) ? Color.BLACK : c);
if (t != null) {
b.setToolTipText(Messages.message(t));
}
b.addActionListener(this);
return b;
}
private void addSuggestion(HashMap<UnitType, Suggestion> suggestions,
UnitType old, UnitType expert,
GoodsType work, int amount) {
Suggestion suggestion = suggestions.get(expert);
// Keep it simple for now.
if (suggestion == null || suggestion.amount < amount) {
suggestions.put(expert, new Suggestion(old, expert, work, amount));
}
}
/**
* Is it a good idea to produce goods at this work location using a
* better unit type?
*
* Always true for colony tiles, but for buildings we need to be
* more conservative or we will end up recommending packing each
* building to capacity.
*
* FTM then:
* - assume that if we have upgraded the building we really do
* want to use it
* - we should produce hammers if we are not, or if we can upgrade
* and existing unit
* - we should produce liberty until we max out the colony SoL
*
* @param wl The <code>WorkLocation</code> where production is to occur.
* @param goodsType The <code>GoodsType</code> to produce.
* @param unit The <code>Unit</code> that is doing the job at present,
* which may be null if none is at work.
* @param expert The expert <code>UnitType</code> to put to work.
* @return True if it is a good idea to use the expert.
*/
private boolean wantGoods(WorkLocation wl, GoodsType goodsType,
Unit unit, UnitType expert) {
boolean ret = false;
if (wl instanceof ColonyTile) {
ret = true;
} else if (wl instanceof Building) {
Building bu = (Building) wl;
Colony colony = wl.getColony();
ret = bu.canAdd(expert)
&& (bu.getLevel() > 1
|| ("model.goods.hammers".equals(goodsType.getId())
&& (colony.getProductionOf(goodsType) == 0
|| (unit != null && unit.getType() != expert)))
|| (goodsType.isLibertyType()
&& colony.getSoL() < 100));
}
return ret;
}
private void addUnits(final HashMap<UnitType, Suggestion> suggestions,
List<UnitType> have, Colony colony, int grow) {
final String action = colony.getId();
final ImageLibrary lib = getGUI().getImageLibrary();
final Specification spec = getSpecification();
String layout = (suggestions.size() <= 1) ? null
: "split " + Integer.toString(suggestions.size());
List<UnitType> types = new ArrayList<UnitType>();
types.addAll(suggestions.keySet());
Collections.sort(types, new Comparator<UnitType>() {
public int compare(UnitType t1, UnitType t2) {
int cmp = suggestions.get(t2).amount
- suggestions.get(t1).amount;
return (cmp != 0) ? cmp
: t1.getId().compareTo(t2.getId());
}
});
for (UnitType type : types) {
boolean present = false;
if (have.contains(type)) {
have.remove(type);
present = true;
}
Suggestion suggestion = suggestions.get(type);
String label = Integer.toString(suggestion.amount);
ImageIcon ii = lib.getUnitImageIcon(type, Role.DEFAULT,
true, 0.333);
StringTemplate tip = (suggestion.oldType == null)
? stpl("report.colony.wanting.description")
.addName("%colony%", colony.getName())
.add("%unit%", type.getNameKey())
.add("%goods%", suggestion.goodsType.getNameKey())
.addAmount("%amount%", suggestion.amount)
: stpl("report.colony.improving.description")
.addName("%colony%", colony.getName())
.add("%oldUnit%", suggestion.oldType.getNameKey())
.add("%unit%", type.getNameKey())
.add("%goods%", suggestion.goodsType.getNameKey())
.addAmount("%amount%", suggestion.amount);
JButton b = colourButton(action, label, ii,
(present) ? cGood : cPlain, tip);
reportPanel.add(b, layout);
layout = null;
}
}
private GoodsType bestProduction(WorkLocation wl, UnitType type) {
if (wl == null) {
return null;
} else if (wl instanceof Building) {
return ((Building) wl).getGoodsOutputType();
} else {
final Specification spec = getSpecification();
List<AbstractGoods> prod = new ArrayList<AbstractGoods>();
for (GoodsType g : spec.getGoodsTypeList()) {
int amount = wl.getPotentialProduction(type, g);
if (amount > 0) prod.add(new AbstractGoods(g, amount));
}
if (prod.isEmpty()) return null;
Collections.sort(prod, abstractGoodsComparator);
return prod.get(0).getType();
}
}
}