freecol/src/net/sf/freecol/client/gui/panel/ColonyPanel.java

2799 lines
98 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.panel;
import static net.sf.freecol.common.util.CollectionUtils.dump;
import static net.sf.freecol.common.util.CollectionUtils.sort;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ComponentInputMap;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolTip;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import net.miginfocom.swing.MigLayout;
import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.FontLibrary;
import net.sf.freecol.client.gui.GUI;
import net.sf.freecol.client.gui.ImageLibrary;
import net.sf.freecol.client.gui.label.GoodsLabel;
import net.sf.freecol.client.gui.label.ProductionLabel;
import net.sf.freecol.client.gui.label.UnitLabel;
import net.sf.freecol.client.gui.panel.FreeColButton.ButtonStyle;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalGap;
import net.sf.freecol.client.gui.panel.WrapLayout.LayoutStyle;
import net.sf.freecol.client.gui.tooltip.RebelToolTip;
import net.sf.freecol.common.debug.DebugUtils;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Ability;
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.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Colony.ColonyChangeEvent;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.ModelMessage;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Player.NoClaimReason;
import net.sf.freecol.common.model.ProductionInfo;
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.UnitLocation.NoAddReason;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.common.util.ImageUtils;
/**
* This is a panel for the Colony display. It shows the units that
* are working in the colony, the buildings and much more.
* <p>
* Beware that in debug mode, this might be a server-side version of
* the colony which is why we need to call getColony().getSpecification()
* to get the spec that corresponds to the good types in this colony.
* <p>
*/
public final class ColonyPanel extends PortPanel
implements PropertyChangeListener {
private static final Logger logger = Logger.getLogger(ColonyPanel.class.getName());
/** The height of the area in whichcomponentRe autoscrolling should happen. */
public static final int SCROLL_AREA_HEIGHT = 40;
/** The speed of the scrolling. */
public static final int SCROLL_SPEED = 40;
final Map<BuildingType, BufferedImage> cachedDefensiveBuildingImage = new HashMap<>();
// Buttons
private JButton unloadButton
= Utility.localizedButton("unload");
private JButton fillButton
= Utility.localizedButton("load");
private JButton warehouseButton = new FreeColButton(Messages.message("colonyPanel.warehouse"))
.withButtonStyle(ButtonStyle.SIMPLE);
private JButton buildQueueButton = new FreeColButton(Messages.message("colonyPanel.buildQueue"))
.withButtonStyle(ButtonStyle.SIMPLE);
private JButton colonyUnitsButton = new FreeColButton(Messages.message("colonyPanel.colonyUnits"))
.withButtonStyle(ButtonStyle.SIMPLE);
// Only present in debug mode
private JButton setGoodsButton = null;
private JButton traceWorkButton = null;
/** The {@code Colony} this panel is displaying. */
private Colony colony;
// inherit PortPanel.pressListener
// inherit PortPanel.defaultTransferHandler
// inherit PortPanel.selectedUnitLabel
private transient MouseListener releaseListener = null;
// Subparts
private final JComboBox<Colony> nameBox = new JComboBox<>();
private JPanel netProductionPanel = null;
private JScrollPane buildingsScroll = null;
private BuildingsPanel buildingsPanel = null;
private JScrollPane cargoScroll = null;
// inherit protected PortPanel.cargoPanel
private ConstructionPanel constructionPanel = null;
private JScrollPane inPortScroll = null;
// inherit protected PortPanel.inPortPanel
private JScrollPane outsideColonyScroll = null;
private OutsideColonyPanel outsideColonyPanel = null;
private PopulationPanel populationPanel = null;
private TilesPanel tilesPanel = null;
private JScrollPane warehouseScroll = null;
private WarehousePanel warehousePanel = null;
private boolean fullscreen = false;
// The action commands
private final ActionListener unloadCmd = ae -> {
final Unit unit = getSelectedUnit();
if (unit == null || !unit.isCarrier()) return;
for (Goods goods : unit.getGoodsList()) {
igc().unloadCargo(goods, false);
}
for (Unit u : unit.getUnitList()) {
igc().leaveShip(u);
}
cargoPanel.update();
updateOutsideColonyPanel();
unloadButton.setEnabled(false);
fillButton.setEnabled(false);
};
private final ActionListener warehouseCmd = ae -> {
getGUI().showWarehouseDialog(getColony(), updated -> {
if (updated) {
updateWarehousePanel();
}
});
};
private final ActionListener buildQueueCmd = ae -> {
getGUI().showBuildQueuePanel(getColony());
updateConstructionPanel();
};
private final ActionListener fillCmd = ae -> {
final Unit unit = getSelectedUnit();
if (unit == null || !unit.isCarrier()) return;
final Colony colony = getColony();
for (Goods goods : unit.getCompactGoodsList()) {
final GoodsType type = goods.getType();
int space = unit.getLoadableAmount(type);
int count = colony.getGoodsCount(type);
if (space > 0 && count > 0) {
int n = Math.min(space, count);
igc().loadCargo(new Goods(goods.getGame(), colony, type, n),
unit);
}
}
};
private final ActionListener colonyUnitsCmd =
ae -> generateColonyUnitsMenu();
private final ActionListener setGoodsCmd = ae -> {
DebugUtils.setColonyGoods(getFreeColClient(), colony);
updateWarehousePanel();
updateProduction();
};
private final ActionListener occupationCmd =
ae -> colony.setOccupationTrace(!colony.getOccupationTrace());
/**
* The constructor for the panel.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param colony The {@code Colony} to display in this panel.
*/
public ColonyPanel(FreeColClient freeColClient, Colony colony) {
super(freeColClient, new MigLayout());
getMigLayout().setLayoutConstraints("fill, wrap 2, insets 0, gap 0 0");
getMigLayout().setColumnConstraints("[fill][474!]");
getMigLayout().setRowConstraints("[]0[]0[][growprio 150,shrinkprio 50][][]");
final Player player = getMyPlayer();
// Do not just use colony.getOwner() == getMyPlayer() because
// in debug mode we are in the *server* colony, and the equality
// will fail.
editable = colony.getOwner().getId().equals(player.getId());
// Only enable the set goods button in debug mode when not spying
if (editable && FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.MENUS)) {
setGoodsButton = Utility.localizedButton("colonyPanel.setGoods");
traceWorkButton = Utility.localizedButton("colonyPanel.traceWork");
}
// Use ESCAPE for closing the ColonyPanel:
InputMap closeIM = new ComponentInputMap(okButton);
closeIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false),
"pressed");
closeIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true),
"released");
SwingUtilities.replaceUIInputMap(okButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, closeIM);
okButton.setText(Messages.message("close"));
InputMap unloadIM = new ComponentInputMap(unloadButton);
unloadIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_U, 0, false),
"pressed");
unloadIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_U, 0, true),
"released");
SwingUtilities.replaceUIInputMap(unloadButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, unloadIM);
InputMap fillIM = new ComponentInputMap(fillButton);
fillIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0, false),
"pressed");
fillIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0, true),
"released");
SwingUtilities.replaceUIInputMap(fillButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, fillIM);
InputMap warehouseIM = new ComponentInputMap(warehouseButton);
warehouseIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false),
"pressed");
warehouseIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true),
"released");
SwingUtilities.replaceUIInputMap(warehouseButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, warehouseIM);
InputMap buildQueueIM = new ComponentInputMap(buildQueueButton);
buildQueueIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0, false),
"pressed");
buildQueueIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0, true),
"released");
SwingUtilities.replaceUIInputMap(buildQueueButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, buildQueueIM);
InputMap colonyUnitsIM = new ComponentInputMap(colonyUnitsButton);
colonyUnitsIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, false),
"pressed");
colonyUnitsIM.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true),
"released");
SwingUtilities.replaceUIInputMap(colonyUnitsButton,
JComponent.WHEN_IN_FOCUSED_WINDOW, colonyUnitsIM);
defaultTransferHandler
= new DefaultTransferHandler(freeColClient, this);
pressListener = new DragListener(freeColClient, this);
releaseListener = new DropListener();
selectedUnitLabel = null;
// Make the colony label
StringBuilder sb = new StringBuilder(32);
String compat = colony.getName();
if (editable) {
for (Colony c : player.getColonyList()) {
this.nameBox.addItem(c);
sb.append(c.getName());
}
} else {
this.nameBox.addItem(colony);
sb.append(colony.getName());
}
Font nameBoxFont = FontLibrary.getScaledFont("header-plain-big", compat);
this.nameBox.setFont(nameBoxFont);
this.nameBox.setBorder(BorderFactory.createEmptyBorder());
this.nameBox.setSelectedItem(colony);
this.nameBox.getInputMap().put(KeyStroke.getKeyStroke("LEFT"),
"selectPrevious2");
this.nameBox.getInputMap().put(KeyStroke.getKeyStroke("RIGHT"),
"selectNext2");
netProductionPanel = new JPanel(new WrapLayout()
.withLayoutStyle(LayoutStyle.PREFER_BOTTOM)
.withHorizontalGap(HorizontalGap.AUTO_BOTTOM, 0, getImageLibrary().scaleInt(5)));
netProductionPanel.setOpaque(false);
buildingsPanel = new BuildingsPanel();
buildingsPanel.setOpaque(false);
buildingsScroll = new JScrollPane(buildingsPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) {
@Override
public Dimension getPreferredSize() {
return new Dimension(1, 1);
}
};
/*
* Dirty workaround for scrollbar flickering and missing layouts. In the future, we
* should instead use the JScrollBar directly from the buildingsPanel (and integrate
* with the layout manager) rather than relying on JScrollPane.
*/
buildingsScroll.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
final Dimension preferredSize = buildingsPanel.getLayout().preferredLayoutSize(buildingsPanel);
final Dimension viewportSize = buildingsScroll.getViewport().getSize();
if (!preferredSize.equals(buildingsPanel.getSize())) {
buildingsPanel.setSize(preferredSize);
buildingsScroll.validate();
}
if (preferredSize.width <= viewportSize.width
&& preferredSize.height <= viewportSize.height) {
buildingsScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
} else {
buildingsScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
}
}
});
buildingsScroll.getVerticalScrollBar().setUnitIncrement(16);
buildingsScroll.getViewport().setOpaque(false);
buildingsScroll.setBorder(null);
buildingsScroll.getViewport().addMouseListener(frameMoveDispatchListener);
buildingsScroll.getViewport().addMouseMotionListener(frameMoveDispatchListener);
cargoPanel = new ColonyCargoPanel(freeColClient);
cargoScroll = new JScrollPane(cargoPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
cargoScroll.setBorder(null);
cargoPanel.addMouseListener(frameMoveDispatchListener);
cargoPanel.addMouseMotionListener(frameMoveDispatchListener);
constructionPanel = new ConstructionPanel(freeColClient, colony, true);
constructionPanel.setForeground(Color.WHITE);
inPortPanel = new ColonyInPortPanel();
inPortScroll = new JScrollPane(inPortPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
inPortScroll.getVerticalScrollBar().setUnitIncrement(16);
inPortScroll.setBorder(null);
inPortScroll.setOpaque(false);
inPortScroll.getViewport().setOpaque(false);
inPortPanel.addMouseListener(frameMoveDispatchListener);
inPortPanel.addMouseMotionListener(frameMoveDispatchListener);
outsideColonyPanel = new OutsideColonyPanel();
outsideColonyScroll = new JScrollPane(outsideColonyPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
outsideColonyScroll.getVerticalScrollBar().setUnitIncrement(16);
outsideColonyScroll.setBorder(null);
outsideColonyScroll.setOpaque(false);
outsideColonyScroll.getViewport().setOpaque(false);
outsideColonyPanel.addMouseListener(frameMoveDispatchListener);
outsideColonyPanel.addMouseMotionListener(frameMoveDispatchListener);
populationPanel = new PopulationPanel();
tilesPanel = new TilesPanel();
warehousePanel = new WarehousePanel();
warehouseScroll = new JScrollPane(warehousePanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
warehouseScroll.setBorder(FreeColImageBorder.colonyWarehouseBorder);
warehousePanel.addMouseListener(frameMoveDispatchListener);
warehousePanel.addMouseMotionListener(frameMoveDispatchListener);
InputMap nameIM = new ComponentInputMap(this.nameBox);
nameIM.put(KeyStroke.getKeyStroke("LEFT"), "selectPrevious2");
nameIM.put(KeyStroke.getKeyStroke("RIGHT"), "selectNext2");
SwingUtilities.replaceUIInputMap(this.nameBox,
JComponent.WHEN_IN_FOCUSED_WINDOW, nameIM);
if (getGUI().getMapViewDimension().height < getImageLibrary().scaleInt(850)) {
/*
* TODO: Remove all borders and show the panel covering the
* entire screen (except for the menu bar.
*/
fullscreen = true;
setBorder(BorderFactory.createEmptyBorder());
} else {
setBorder(FreeColImageBorder.innerColonyPanelBorder);
}
setColony(colony);
initialize();
if (!fullscreen) {
final Dimension startingSize = getFreeColClient().getGUI().getFixedImageLibrary().scale(new Dimension(1280, 700));
getGUI().restoreSavedSize(this, startingSize);
}
setEscapeAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ae) {
okButton.doClick();
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void refreshLayout() {
final Colony theColony = colony;
closeColonyPanel();
getGUI().showColonyPanel(theColony, null);
}
/**
* Paints parts of the background/skins for the ColonyPanel. In the future
* we will probably want to generalize this approach in order to allow
* mods to define complex skins.
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
/*
* Painting the backgrounds here avoids the need for a JLayeredPane (since we
* want different backgrounds to overlay with partial transparency).
*/
final Dimension size = getSize();
final BufferedImage colonyDocksBackground = getImageLibrary().getColonyDocksBackground();
if (colonyDocksBackground != null) {
final int docksBottomLeftX = inPortScroll.getX();
final int docksBottomLeftY = inPortScroll.getY() + inPortScroll.getHeight();
int y = docksBottomLeftY - colonyDocksBackground.getHeight();
g.drawImage(colonyDocksBackground, docksBottomLeftX, y, null);
final BufferedImage colonyDocksSkyBackground = getImageLibrary().getColonyDocksSkyBackground();
if (colonyDocksSkyBackground != null) {
while (y > 0) {
y -= colonyDocksSkyBackground.getHeight();
g.drawImage(colonyDocksSkyBackground, docksBottomLeftX, y, null);
}
}
}
final Building docksBuilding = colony.getBuildings()
.stream()
.filter(b -> b.hasAbility(Ability.PRODUCE_IN_WATER))
.sorted(Comparator.comparing(Building::getId)) // Stable sort, but there should only be one value.
.findFirst()
.orElse(null);
final BufferedImage docks = getImageLibrary().getScaledBuildingImage(docksBuilding);
if (docks != null) {
final int docksBottomLeftX = inPortScroll.getX();
final int docksBottomLeftY = inPortScroll.getY() + inPortScroll.getHeight();
final int y = docksBottomLeftY - docks.getHeight();
g.drawImage(docks, docksBottomLeftX, y, null);
}
final List<Building> defensiveBuildings = colony.getBuildings()
.stream()
.filter(b -> b.getType().isDefenceType())
.sorted(Comparator.comparing(Building::getId)) // Stable sort. Might add a z-index property later.
.collect(Collectors.toList());
if (defensiveBuildings.isEmpty()) {
paintOutsideColonyBackground(g2d, null);
}
for (Building defensiveBuilding : defensiveBuildings) {
paintOutsideColonyBackground(g2d, defensiveBuilding.getType());
}
final BufferedImage unavailable = getImageLibrary().getScaledCargoHold(false);
if (unavailable != null) {
final int cargoHoldTopRightX = cargoScroll.getX() + cargoScroll.getWidth();
final int cargoHoldTopRightY = cargoScroll.getY();
g.drawImage(unavailable, cargoHoldTopRightX, cargoHoldTopRightY, null);
}
final BufferedImage upperRightBackground = getImageLibrary().getColonyUpperRightBackground();
if (upperRightBackground != null) {
final Insets insets = getInsets();
int x = size.width - upperRightBackground.getWidth() - insets.right;
int y = insets.top;
g.drawImage(upperRightBackground, x, y, null);
Font numberFont = FontLibrary.getUnscaledFont(Utility.FONTSPEC_TITLE, "1").deriveFont((float) getImageLibrary().scaleInt(32));
final Font origFont = g.getFont();
g.setFont(numberFont);
FontMetrics fm = g2d.getFontMetrics();
final String colonySize = "" + colony.getUnitCount();
double centerY = (fm.getAscent() - fm.getDescent() - fm.getLeading()) / 2;
final int colonySizeX = getImageLibrary().scaleInt(100);
final int colonySizeY = getImageLibrary().scaleInt(40);
final double colonySizeCenterX = -fm.getStringBounds(colonySize, g2d).getWidth() / 2;
g.drawString(
colonySize,
(int) (x + colonySizeCenterX + colonySizeX),
(int) (y + centerY + colonySizeY)
);
final String bonus;
final int grow = colony.getPreferredSizeChange();
if (grow > 9) {
bonus = ">9";
} else if (grow >= 0) {
bonus = "+" + grow;
} else {
bonus = "" + grow;
}
final int colonyBonusX = getImageLibrary().scaleInt(404);
final double colonyBonusCenterX = -fm.getStringBounds(bonus, g2d).getWidth() / 2;
g.drawString(
bonus,
(int) (x + colonyBonusCenterX + colonyBonusX),
(int) (y + centerY + colonySizeY)
);
numberFont = numberFont.deriveFont((float) getImageLibrary().scaleInt(18));
g.setFont(numberFont);
fm = g2d.getFontMetrics();
centerY = (fm.getAscent() - fm.getDescent() - fm.getLeading()) / 2;
final String rebelPercentage = colony.getSonsOfLiberty() + "%";
final int rebelPercentageX = getImageLibrary().scaleInt(101);
final int rebelPercentageY = getImageLibrary().scaleInt(193);
final double rebelPercentageCenterX = -fm.getStringBounds(rebelPercentage, g2d).getWidth() / 2;
g.drawString(
rebelPercentage,
(int) (x + rebelPercentageCenterX + rebelPercentageX),
(int) (y + centerY + rebelPercentageY)
);
final String royalistPercentage = (100 - colony.getSonsOfLiberty()) + "%";
final int royalistPercentageX = getImageLibrary().scaleInt(407);
final double royalistPercentageCenterX = -fm.getStringBounds(royalistPercentage, g2d).getWidth() / 2;
g.drawString(
royalistPercentage,
(int) (x + royalistPercentageCenterX + royalistPercentageX),
(int) (y + centerY + rebelPercentageY)
);
g.setFont(origFont);
}
final BufferedImage nwBorder = getImageLibrary().getScaledImage("image.border.wooden.nw");
final BufferedImage wBorder = getImageLibrary().getScaledImage("image.border.wooden.w");
if (nwBorder != null && wBorder != null) {
final int x = okButton.getX() + okButton.getWidth();
int y = okButton.getY();
g.drawImage(nwBorder,
x,
y,
null);
y += nwBorder.getHeight();
while (y < size.height) {
g.drawImage(wBorder,
x,
y,
null);
y += wBorder.getHeight();
}
}
final BufferedImage sBorder = getImageLibrary().getScaledImage("image.border.wooden.s");
if (sBorder != null) {
int x = 0;
int y = warehouseScroll.getY() + warehouseScroll.getHeight();
while (x < size.width) {
g.drawImage(sBorder, x, y, null);
x += sBorder.getWidth();
}
}
if (nwBorder != null && wBorder != null) {
int x = warehouseScroll.getX() - nwBorder.getWidth();
int y = warehouseScroll.getY();
g.drawImage(nwBorder, x, y, null);
y += nwBorder.getHeight();
while (y < size.height) {
g.drawImage(wBorder,
x,
y,
null);
y += wBorder.getHeight();
}
}
}
private void paintOutsideColonyBackground(Graphics2D g2d, BuildingType buildingType) {
final Dimension outsideColonySize = outsideColonyScroll.getSize();
BufferedImage outsideColonyImage = cachedDefensiveBuildingImage.get(buildingType);
if (outsideColonyImage == null
|| outsideColonyImage.getWidth() != outsideColonySize.width
|| outsideColonyImage.getHeight() != outsideColonySize.height) {
outsideColonyImage = ImageLibrary.getUncachedOutsideColonyBackground(buildingType, outsideColonySize);
cachedDefensiveBuildingImage.put(buildingType, outsideColonyImage);
}
if (outsideColonyImage == null) {
return;
}
final int x = outsideColonyScroll.getX();
final int y = outsideColonyScroll.getY();
g2d.drawImage(outsideColonyImage, x, y, null);
}
/**
* {@inheritDoc}
*/
@Override
public Border getFrameBorder() {
if (fullscreen) {
return BorderFactory.createEmptyBorder();
}
return FreeColImageBorder.outerColonyPanelBorder;
}
public boolean isFullscreen() {
return fullscreen;
}
/**
* Set the current colony.
*
* @param colony The new colony value.
*/
private synchronized void setColony(Colony colony) {
this.colony = colony;
}
private Dimension getTilesPanelGuiScaledDimension() {
final int tilesPanelWidth = 3 * getImageLibrary().getTileSize().width;
final int tilesPanelHeight = 3 * getImageLibrary().getTileSize().height;
final Dimension size = getImageLibrary().getTileSize();
final int extraHeight = tilesPanelTopOffset(size);
return new Dimension(tilesPanelWidth, tilesPanelHeight + extraHeight);
}
/**
* Initialize the entire panel.
*/
private void initialize() {
cleanup();
addPropertyChangeListeners();
addMouseListeners();
setTransferHandlers(isEditable());
// Enable/disable widgets
unloadButton.addActionListener(unloadCmd);
fillButton.addActionListener(fillCmd);
warehouseButton.addActionListener(warehouseCmd);
buildQueueButton.addActionListener(buildQueueCmd);
colonyUnitsButton.addActionListener(colonyUnitsCmd);
if (setGoodsButton != null) {
setGoodsButton.addActionListener(setGoodsCmd);
}
if (traceWorkButton != null) {
traceWorkButton.addActionListener(occupationCmd);
}
unloadButton.setEnabled(isEditable());
fillButton.setEnabled(isEditable());
warehouseButton.setEnabled(isEditable());
buildQueueButton.setEnabled(isEditable());
colonyUnitsButton.setEnabled(isEditable());
if (setGoodsButton != null) {
setGoodsButton.setEnabled(isEditable());
}
if (traceWorkButton != null) {
traceWorkButton.setEnabled(isEditable());
}
final GUI gui = getGUI();
this.nameBox.setEnabled(isEditable());
this.nameBox.addActionListener((ActionEvent ae) -> {
final Colony newColony = (Colony)nameBox.getSelectedItem();
closeColonyPanel();
gui.showColonyPanel(newColony, null);
});
updateNetProductionPanel();
buildingsPanel.initialize();
cargoPanel.initialize();
constructionPanel.initialize();
inPortPanel.initialize();
outsideColonyPanel.initialize();
populationPanel.initialize();
tilesPanel.initialize();
warehousePanel.initialize();
final Dimension tilesScrollDimension = getTilesPanelGuiScaledDimension();
add(buildingsScroll, "span 1 4, grow");
// TODO: Display the name:
//add(this.nameBox, "grow, span");
JPanel upperRightPanel = new JPanel(new MigLayout("wrap 1, ins 7 7 7 7, gap 0 0", "[center]")) {
{
/*
* XXX: This hack has been used before for creating the tooltip,
* but there have to be another better solution.
*/
setToolTipText(" ");
}
@Override
public JToolTip createToolTip() {
JToolTip toolTip = new RebelToolTip(getFreeColClient(), getColony());
return toolTip;
}
};
upperRightPanel.setOpaque(false);
upperRightPanel.add(tilesPanel, "width " + tilesScrollDimension.width + "px!, height " + tilesScrollDimension.height +"px!, top, center");
final FreeColImageBorder border = FreeColImageBorder.innerColonyPanelBorder;
final Insets borderInsets = border.getBorderInsets(this);
upperRightPanel.add(netProductionPanel, "grow, wmax 384, height 68!");
// TODO: Display the numbers in new components:
//add(populationPanel, "grow");
upperRightPanel.add(constructionPanel, "grow, top, wmax 367, height 90");
add(upperRightPanel, "span 1 3, growy, gapbefore 53, width 400!, wmax 400");
add(inPortScroll, "span, grow, height 96!, gaptop push");
add(outsideColonyScroll, "grow, height 96!");
add(cargoScroll, "grow, height 100!");
add(warehouseScroll, "span, split, height 48!, growx");
add(wrapWithBorder(warehouseButton), "height 48!, gap 0 0 0 0");
if (setGoodsButton != null) {
add(wrapWithBorder(setGoodsButton), "height 48!, gap 0 0 0 0");
}
if (traceWorkButton != null) {
add(wrapWithBorder(traceWorkButton), "height 48!, gap 0 0 0 0");
}
/*
* Don't think we need these, but they can be added to the
* in-port panel if we do. For now, adding them with zero
* size in order to keep the accelerator keys (and we should
* keep those):
*/
add(unloadButton, "wmax 0, height 0, gap 0 0 0 0");
add(fillButton, "wmax 0, height 0, gap 0 0 0 0");
add(okButton, "gapbefore push, height 48!"); // tag ok
update();
}
private JPanel wrapWithBorder(JComponent c) {
final JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(FreeColImageBorder.woodenPanelBorder);
panel.add(c);
return panel;
}
/**
* Clean up this colony panel.
*/
private void cleanup() {
unloadButton.removeActionListener(unloadCmd);
fillButton.removeActionListener(fillCmd);
warehouseButton.removeActionListener(warehouseCmd);
buildQueueButton.removeActionListener(buildQueueCmd);
colonyUnitsButton.removeActionListener(colonyUnitsCmd);
if (setGoodsButton != null) {
setGoodsButton.removeActionListener(setGoodsCmd);
}
if (traceWorkButton != null) {
traceWorkButton.removeActionListener(occupationCmd);
}
removePropertyChangeListeners();
if (getSelectedUnit() != null) {
getSelectedUnit().removePropertyChangeListener(this);
}
removeMouseListeners();
for (MouseListener listener : getMouseListeners()) {
removeMouseListener(listener);
}
setTransferHandlers(false);
buildingsPanel.cleanup();
cargoPanel.cleanup();
constructionPanel.cleanup();
inPortPanel.cleanup();
outsideColonyPanel.cleanup();
populationPanel.cleanup();
tilesPanel.cleanup();
warehousePanel.cleanup();
removeAll();
}
private void addMouseListeners() {
if (isEditable()) {
cargoPanel.addMouseListener(releaseListener);
inPortPanel.addMouseListener(releaseListener);
outsideColonyPanel.addMouseListener(releaseListener);
warehousePanel.addMouseListener(releaseListener);
}
}
private void removeMouseListeners() {
cargoPanel.removeMouseListener(releaseListener);
inPortPanel.removeMouseListener(releaseListener);
outsideColonyPanel.removeMouseListener(releaseListener);
warehousePanel.removeMouseListener(releaseListener);
}
private void setTransferHandlers(boolean enable) {
DefaultTransferHandler dth = (enable) ? defaultTransferHandler : null;
cargoPanel.setTransferHandler(dth);
inPortPanel.setTransferHandler(dth);
outsideColonyPanel.setTransferHandler(dth);
warehousePanel.setTransferHandler(dth);
}
/**
* Add property change listeners needed by this ColonyPanel.
*/
private void addPropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.addPropertyChangeListener(this);
colony.getGoodsContainer().addPropertyChangeListener(this);
colony.getTile().addPropertyChangeListener(this);
}
}
/**
* Remove the property change listeners of ColonyPanel.
*/
private void removePropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.removePropertyChangeListener(this);
colony.getGoodsContainer().removePropertyChangeListener(this);
colony.getTile().removePropertyChangeListener(this);
}
}
/**
* Update all the production-related panels.
*
* This has to be very broad as a change at one work location can
* have a secondary effect on another, and especially if the
* population hits a bonus boundary. A simple example is that
* adding extra lumber production may improve the production of the
* lumber mill. These changes can then flow on to production and
* construction displays.
*/
private void updateProduction() {
final Colony colony = getColony();
if (colony == null) return;
// Check for non-producing locations that can now produce.
for (WorkLocation wl : colony.getCurrentWorkLocationsList()) {
boolean change = false, check = wl.getProductionType() == null;
for (Unit unit : transform(wl.getUnits(), u ->
(check || !wl.produces(u.getWorkType())))) {
GoodsType workType = wl.getWorkFor(unit);
if (workType != null && workType != unit.getWorkType()) {
change |= igc().changeWorkType(unit, workType);
}
}
if (change) wl.updateProductionType();
}
updateTilesPanel();
updateBuildingsPanel();
updateNetProductionPanel();
updateConstructionPanel();
}
/**
* Update the entire colony panel.
*/
public void update() {
buildingsPanel.update();
constructionPanel.update();
inPortPanel.update();
updateNetProductionPanel();
outsideColonyPanel.update();
populationPanel.update();
tilesPanel.update();
warehousePanel.update();
}
/**
* Enables the unload and fill buttons if the currently selected unit is a
* carrier with some cargo.
*/
private void updateCarrierButtons() {
final Colony colony = getColony();
unloadButton.setEnabled(false);
fillButton.setEnabled(false);
if (selectedUnitLabel != null && isEditable()) {
Unit unit = selectedUnitLabel.getUnit();
if (unit != null && unit.isCarrier() && unit.hasCargo()) {
unloadButton.setEnabled(true);
for (Goods goods : unit.getCompactGoodsList()) {
if (colony.getGoodsCount(goods.getType()) > 0) {
fillButton.setEnabled(true);
break;
}
}
}
}
}
/**
* Generates a menu containing the units currently accessible
* from the Colony Panel allowing keyboard access to said units.
*/
private void generateColonyUnitsMenu() {
final FreeColClient freeColClient = getFreeColClient();
ImageLibrary lib = getImageLibrary();
final Colony colony = getColony();
JPopupMenu colonyUnitsMenu
= new JPopupMenu(Messages.message("colonyPanel.colonyUnits"));
ImageIcon unitIcon = null;
final QuickActionMenu unitMenu
= new QuickActionMenu(freeColClient, this);
Tile colonyTile = colony.getTile();
int unitNumber = 0;
JMenuItem subMenu = null;
for (final Unit unit : colony.getUnitList()) {
WorkLocation wl = unit.getWorkLocation();
GoodsType goodsType = unit.getWorkType();
Unit student = unit.getStudent();
unitIcon = new ImageIcon(lib.getSmallerUnitImage(unit));
StringBuilder sb = new StringBuilder(64);
String prodLabel = Messages.message("colonyPanel.producing");
if (student != null) {
sb.append(unit.getDescription())
.append(' ').append(prodLabel).append(' ')
.append(Messages.getName(unit.getType()
.getSkillTaught()))
.append(' ')
.append(unit.getTurnsOfTraining())
.append('/')
.append(unit.getNeededTurnsOfTraining());
} else if (wl != null && goodsType != null) {
int producing = wl.getProductionOf(unit, goodsType);
sb.append(unit.getDescription())
.append(' ').append(prodLabel).append(' ')
.append(producing)
.append(' ')
.append(Messages.message(StringTemplate.template(goodsType)
.addAmount("%amount%", producing)));
} else {
sb.append(unit.getDescription())
.append(' ').append(prodLabel).append(' ')
.append(Messages.message("nothing"));
}
String menuTitle = sb.toString();
subMenu = new JMenuItem(menuTitle, unitIcon);
subMenu.addActionListener((ActionEvent ae) -> {
unitMenu.addMenuItems(new UnitLabel(freeColClient, unit));
getGUI().showPopupMenu(unitMenu, 0, 0);
});
unitNumber++;
colonyUnitsMenu.add(subMenu);
}
colonyUnitsMenu.addSeparator();
for (final Unit unit : colonyTile.getUnitList()) {
if (unit.isCarrier()) {
unitIcon = new ImageIcon(lib.getSmallerUnitImage(unit));
String menuTitle = unit.getDescription()
+ " " + Messages.message("colonyPanel.inPort");
subMenu = new JMenuItem(menuTitle, unitIcon);
subMenu.addActionListener((ActionEvent ae) -> {
unitMenu.addMenuItems(new UnitLabel(freeColClient, unit));
getGUI().showPopupMenu(unitMenu, 0, 0);
});
unitNumber++;
colonyUnitsMenu.add(subMenu);
for (final Unit innerUnit : unit.getUnitList()) {
unitIcon = new ImageIcon(lib.getSmallerUnitImage(innerUnit));
menuTitle = innerUnit.getDescription()
+ " " + Messages.message("cargoOnCarrier")
+ " " + unit.getDescription();
subMenu = new JMenuItem(menuTitle, unitIcon);
subMenu.addActionListener((ActionEvent ae) -> {
unitMenu.addMenuItems(new UnitLabel(freeColClient, innerUnit));
getGUI().showPopupMenu(unitMenu, 0, 0);
});
unitNumber++;
colonyUnitsMenu.add(subMenu);
}
} else if (!unit.isOnCarrier()) {
unitIcon = new ImageIcon(lib.getSmallerUnitImage(unit));
String menuTitle = unit.getDescription()
+ " " + Messages.message("colonyPanel.outsideColony");
subMenu = new JMenuItem(menuTitle, unitIcon);
subMenu.addActionListener((ActionEvent ae) -> {
unitMenu.addMenuItems(new UnitLabel(freeColClient, unit));
getGUI().showPopupMenu(unitMenu, 0, 0);
});
unitNumber++;
colonyUnitsMenu.add(subMenu);
}
}
colonyUnitsMenu.addSeparator();
int elements = colonyUnitsMenu.getSubElements().length;
if (elements > 0) {
int lastIndex = colonyUnitsMenu.getComponentCount() - 1;
if (colonyUnitsMenu.getComponent(lastIndex) instanceof JPopupMenu.Separator) {
colonyUnitsMenu.remove(lastIndex);
}
}
getGUI().showPopupMenu(colonyUnitsMenu, 0, 0);
}
/**
* Try to assign a unit to work in a colony work location.
*
* @param unit The {@code Unit} that is to work.
* @param wl The {@code WorkLocation} to work in.
* @return True if the unit is now in the work location.
*/
private boolean tryWork(Unit unit, WorkLocation wl) {
// Choose the best work type.
GoodsType workType = wl.getWorkFor(unit);
// Set the unit to work. Note this might upgrade the unit,
// and possibly even change its work type as the server has
// the right to maintain consistency.
igc().work(unit, wl);
// Did it work?
if (unit.getLocation() != wl) return false;
// Now recheck, and see if we want to change to the expected
// work type.
if (workType != null && workType != unit.getWorkType()) {
igc().changeWorkType(unit, workType);
}
return true;
}
// Public base accessors and mutators
/**
* Gets the {@code Colony} in use.
*
* Try to use this at the top of all the routines that need the colony,
* to get a stable value. There have been nasty races when the colony
* changes out from underneath us, and more may be lurking.
*
* @return The {@code Colony}.
*/
public final synchronized Colony getColony() {
return colony;
}
/**
* Gets the {@code TilesPanel}-object in use.
*
* @return The {@code TilesPanel}.
*/
public final TilesPanel getTilesPanel() {
return tilesPanel;
}
/**
* Gets the {@code WarehousePanel}-object in use.
*
* @return The {@code WarehousePanel}.
*/
public final WarehousePanel getWarehousePanel() {
return warehousePanel;
}
// Override PortPanel
/**
* {@inheritDoc}
*/
@Override
public void setSelectedUnitLabel(UnitLabel unitLabel) {
if (selectedUnitLabel != unitLabel) {
inPortPanel.removePropertyChangeListeners();
if (selectedUnitLabel != null) {
selectedUnitLabel.setSelected(false);
selectedUnitLabel.getUnit().removePropertyChangeListener(this);
}
super.setSelectedUnitLabel(unitLabel);
if (unitLabel == null) {
cargoPanel.setCarrier(null);
} else {
cargoPanel.setCarrier(unitLabel.getUnit());
unitLabel.setSelected(true);
unitLabel.getUnit().addPropertyChangeListener(this);
}
inPortPanel.addPropertyChangeListeners();
}
updateCarrierButtons();
inPortPanel.revalidate();
inPortPanel.repaint();
}
/**
* {@inheritDoc}
*/
@Override
public boolean setSelectedUnit(Unit unit) {
return ((unit.isCarrier()) ? inPortPanel.setSelectedUnit(unit)
: super.setSelectedUnit(unit));
}
// Update routines
private void updateBuildingsPanel() {
// FIXME: find out why the cache needs to be explicitly invalidated
colony.invalidateCache();
buildingsPanel.update();
}
private void updateConstructionPanel() {
constructionPanel.update();
}
private void updateInPortPanel() {
inPortPanel.update();
}
private void updateNetProductionPanel() {
final FreeColClient freeColClient = getFreeColClient();
final Colony colony = getColony();
final Specification spec = colony.getSpecification();
// FIXME: find out why the cache needs to be explicitly invalidated
colony.invalidateCache();
netProductionPanel.removeAll();
for (GoodsType goodsType : spec.getGoodsTypeList()) {
int amount = colony.getAdjustedNetProductionOf(goodsType);
if (amount != 0) {
AbstractGoods ag = new AbstractGoods(goodsType, amount);
netProductionPanel.add(new ProductionLabel(freeColClient, ag));
}
}
netProductionPanel.revalidate();
netProductionPanel.repaint();
}
private void updateOutsideColonyPanel() {
outsideColonyPanel.update();
}
private void updatePopulationPanel() {
populationPanel.update();
}
private void updateTilesPanel() {
tilesPanel.update();
}
private void updateWarehousePanel() {
warehousePanel.update();
}
/**
* Close this {@code ColonyPanel}.
*/
public void closeColonyPanel() {
final Colony colony = getColony();
final Player player = getMyPlayer();
boolean abandon = false;
BuildableType buildable;
if (player.owns(colony)) {
if (colony.getUnitCount() == 0) {
final String key = (player.isRebel()
&& player.getNumberOfPorts() == 1
&& colony.isConnectedPort())
? "abandonColony.lastPort.text"
: "abandonColony.text";
if (!getGUI().confirm(null, StringTemplate.key(key), colony,
"abandonColony.yes", "abandonColony.no")) return;
abandon = true;
} else if ((buildable = colony.getCurrentlyBuilding()) != null) {
int required = buildable.getRequiredPopulation();
if (required > colony.getUnitCount()
&& !getGUI().confirm(null, StringTemplate
.template("colonyPanel.reducePopulation")
.addName("%colony%", colony.getName())
.addAmount("%number%", required)
.addNamed("%buildable%", buildable),
colony, "ok", "cancel")) return;
}
}
cleanup();
getGUI().removeComponent(this);
// Abandon colony and active unit handling is in IGC
igc().closeColony(colony, abandon);
}
// Interface PortPanel
/**
* Gets the list of units on the colony tile.
* Note, does not include the units *inside* the colony.
*
* @return A sorted list of units on the colony tile.
*/
@Override
public List<Unit> getUnitList() {
return sort(colony.getTile().getUnits());
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
if (OK.equals(ae.getActionCommand())) {
closeColonyPanel();
} else {
super.actionPerformed(ae);
}
}
// Interface PropertyChangeListener
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
final Colony colony = getColony();
if (!isShowing() || colony == null) return;
String property = event.getPropertyName();
logger.finest(colony.getName() + " change " + property
+ ": " + event.getOldValue()
+ " -> " + event.getNewValue());
if (property == null) {
logger.warning("Null property change");
} else if (Unit.CARGO_CHANGE.equals(property)) {
cargoPanel.update();
updateInPortPanel();
} else if (ColonyChangeEvent.POPULATION_CHANGE.toString().equals(property)) {
updatePopulationPanel();
updateNetProductionPanel(); // food production changes
} else if (ColonyChangeEvent.BONUS_CHANGE.toString().equals(property)) {
if (colony.getUnitCount() > 0) { // Ignore messages when abandoning
ModelMessage msg = colony.checkForGovMgtChangeMessage();
if (msg != null) {
getGUI().showInformationPanel(colony, msg);
}
}
updatePopulationPanel();
} else if (ColonyChangeEvent.BUILD_QUEUE_CHANGE.toString().equals(property)) {
updateProduction();
} else if (ColonyChangeEvent.UNIT_TYPE_CHANGE.toString().equals(property)) {
FreeColGameObject object = (FreeColGameObject)event.getSource();
UnitType oldType = (UnitType) event.getOldValue();
UnitType newType = (UnitType) event.getNewValue();
getGUI().showInformationPanel(object, StringTemplate
.template("colonyPanel.unitChange")
.addName("%colony%", colony.getName())
.addNamed("%oldType%", oldType)
.addNamed("%newType%", newType));
updateTilesPanel();
updateBuildingsPanel();
updateProduction();
} else if (property.startsWith("model.goods.")) {
// Changes to warehouse goods count may affect building production
// which requires a view update.
updateWarehousePanel();
updateProduction();
} else if (Tile.UNIT_CHANGE.equals(property)) {
updateOutsideColonyPanel();
updateInPortPanel();
updatePopulationPanel();
updateWarehousePanel();// Role change changes equipment goods
} else if (Unit.MOVE_CHANGE.equals(property)) {
updateOutsideColonyPanel();
} else {
// ColonyTiles and Buildings now have their own
// propertyChangeListeners so {ColonyTile,Building}.UNIT_CHANGE
// events should not arrive here.
logger.warning("Unknown property change event: "
+ event.getPropertyName());
}
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void removeNotify() {
if (colony == null) return; // Been here already
colony.setOccupationTrace(false);
colony = null; // Now Canvas.getColonyPanel will no longer find this
super.removeNotify();
// Alas, ColonyPanel is often leaky.
unloadButton = null;
fillButton = null;
warehouseButton = null;
buildQueueButton = null;
colonyUnitsButton = null;
setGoodsButton = null;
traceWorkButton = null;
netProductionPanel = null;
buildingsPanel = null;
buildingsScroll = null;
cargoScroll = null;
constructionPanel = null;
inPortScroll = null;
outsideColonyPanel = null;
outsideColonyScroll = null;
populationPanel = null;
tilesPanel = null;
warehousePanel = null;
warehouseScroll = null;
releaseListener = null;
// Inherited from PortPanel
//cargoPanel = null;
//inPortPanel = null;
//defaultTransferHandler = null;
//pressListener = null;
//selectedUnitLabel = null;
}
// Subpanel classes
/**
* This panel shows the content of a carrier in the colony
*/
public final class ColonyCargoPanel extends CargoPanel {
/**
* Create this colony cargo panel.
*
* @param freeColClient The {@code FreeColClient} for the game.
*/
public ColonyCargoPanel(FreeColClient freeColClient) {
super(freeColClient, false, true);
}
/**
* {@inheritDoc}
*/
@Override
public void update() {
super.update();
// May have un/loaded cargo, "Unload" could have changed validity
updateCarrierButtons();
updatePopulationPanel();
}
}
/**
* The panel to display the population breakdown for this colony.
*/
public final class PopulationPanel extends MigPanel {
// Predefine all the required labels.
private final JLabel rebelShield = new JLabel();
private final JLabel rebelLabel = new JLabel();
private final JLabel bonusLabel = new JLabel();
private final JLabel royalistLabel = new JLabel();
private final JLabel royalistShield = new JLabel();
private final JLabel rebelMemberLabel = new JLabel();
private final JLabel popLabel = new JLabel();
private final JLabel royalistMemberLabel = new JLabel();
/**
* Create a new population panel.
*/
public PopulationPanel() {
super("PopulationPanelUI",
new MigLayout("wrap 5, fill, insets 0",
"[][]:push[center]:push[right][]"));
setOpaque(false);
setToolTipText(" ");
}
/**
* Initialize this population panel.
*/
public void initialize() {
cleanup();
update();
}
/**
* Clean up this population panel.
*/
public void cleanup() {
// Nothing yet
}
/**
* Update this population panel.
*/
public void update() {
final Colony colony = getColony();
if (colony == null) return;
final ImageLibrary lib = getImageLibrary();
final Font font = FontLibrary.getScaledFont("normal-plain-smaller", null);
final int uc = colony.getUnitCount();
final int solPercent = colony.getSonsOfLiberty();
final int rebelCount = Colony.calculateRebelCount(uc, solPercent);
final Nation nation = colony.getOwner().getNation();
final int grow = colony.getPreferredSizeChange();
final int bonus = colony.getProductionBonus();
StringTemplate t;
removeAll();
rebelShield.setIcon(new ImageIcon(lib.getSmallerNationImage(nation)));
add(rebelShield, "bottom");
t = StringTemplate.template("colonyPanel.rebelLabel")
.addAmount("%number%", rebelCount);
rebelLabel.setText(Messages.message(t));
rebelLabel.setFont(font);
add(rebelLabel, "split 2, flowy");
rebelMemberLabel.setText(solPercent + "%");
rebelMemberLabel.setFont(font);
add(rebelMemberLabel);
t = StringTemplate.template("colonyPanel.populationLabel")
.addAmount("%number%", uc);
popLabel.setText(Messages.message(t));
popLabel.setFont(font);
add(popLabel, "split 2, flowy");
String growth = (grow == 0) ? ""
: '(' + ((grow >= Colony.CHANGE_UPPER_BOUND)
? Messages.message("many")
: String.valueOf(grow)) + ')';
t = StringTemplate.template("colonyPanel.bonusLabel")
.addAmount("%number%", bonus)
.addName("%extra%", growth);
bonusLabel.setText(Messages.message(t));
bonusLabel.setFont(font);
add(bonusLabel);
t = StringTemplate.template("colonyPanel.royalistLabel")
.addAmount("%number%", uc - rebelCount);
royalistLabel.setText(Messages.message(t));
royalistLabel.setFont(font);
add(royalistLabel, "split 2, flowy");
royalistMemberLabel.setText((100 - solPercent) + "%");
royalistMemberLabel.setFont(font);
add(royalistMemberLabel);
Nation other = (colony.getOwner().isREF())
? nation.getRebelNation()
: nation.getREFNation();
try {
royalistShield.setIcon(new ImageIcon(lib.getSmallerNationImage(other)));
add(royalistShield, "bottom");
} catch (Exception e) {
logger.log(Level.WARNING, "Shield: " + nation + "/" + other, e);
}
revalidate();
repaint();
}
/**
* Generates the {@code JToolTip} with the colony's
* detailed population information.
*
* @return The JToolTip created.
*/
@Override
public JToolTip createToolTip() {
return new RebelToolTip(getFreeColClient(), getColony());
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void removeNotify() {
super.removeNotify();
removeAll();
setLayout(null);
}
}
/**
* A panel that holds UnitLabels that represent Units that are standing in
* front of a colony.
*/
public final class OutsideColonyPanel extends UnitPanel implements DropTarget {
/**
* Create this OutsideColonyPanel.
*/
public OutsideColonyPanel() {
super("OutsideColonyPanelUI",
new MigLayout("ins 0, gapy 0, align center bottom", "", "[bottom]"),
ColonyPanel.this,
null, ColonyPanel.this.isEditable());
/*
* TODO: Decide if we need to have a title for "Outside the colony". If so,
* make it nicer than this one.
*/
//setBorder(Utility.localizedBorder("colonyPanel.outsideColony"));
setOpaque(false);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize() {
super.initialize();
final Colony colony = getColony();
if (colony != null) {
setName(colony.getName() + " - " + Messages.message("port"));
}
}
/**
* {@inheritDoc}
*/
@Override
public void cleanup() {
super.cleanup();
removeAll();
}
/**
* {@inheritDoc}
*/
@Override
protected void addPropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.getTile().addPropertyChangeListener(Tile.UNIT_CHANGE,
this);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void removePropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.getTile().removePropertyChangeListener(Tile.UNIT_CHANGE,
this);
}
}
// Inherit UnitPanel.update
// Interface DropTarget
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Goods goods) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(GoodsType goodsType) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Unit unit) {
return !unit.isCarrier();
}
/**
* {@inheritDoc}
*/
@Override
public void selectLabel() {
// do nothing
}
/**
* {@inheritDoc}
*/
@Override
public Component add(Component comp, boolean editState) {
Container oldParent = comp.getParent();
if (editState) {
if (comp instanceof UnitLabel) {
UnitLabel unitLabel = (UnitLabel)comp;
Unit unit = unitLabel.getUnit();
if (!unit.isOnCarrier()) {
igc().putOutsideColony(unit);
}
if (unit.getColony() == null) {
closeColonyPanel();
return null;
} else if (!unit.isOnTile() && !unit.isOnCarrier()) {
return null;
}
oldParent.remove(comp);
initialize();
return comp;
} else {
logger.warning("Invalid component: " + comp);
return null;
}
} else {
((UnitLabel)comp).setSmall(false);
return add(comp);
}
}
/**
* {@inheritDoc}
*/
@Override
public int suggested(GoodsType type) { return -1; } // N/A
}
/**
* A panel that holds UnitLabels that represent naval Units that are
* waiting in the port of the colony.
*/
public final class ColonyInPortPanel extends InPortPanel {
/**
* Creates this ColonyInPortPanel.
*/
public ColonyInPortPanel() {
super(new MigLayout("ins 0, gapy 0, align center bottom", "", "[bottom]"), ColonyPanel.this,
null, ColonyPanel.this.isEditable());
/*
* TODO: Decide if we need to have a title for "In port". If so,
* make it nicer than this one.
*/
//setTitleInsidePanel(Messages.message("colonyPanel.inPort"));
setOpaque(false);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize() {
super.initialize();
final Colony colony = getColony();
if (colony != null) {
setName(colony.getName() + " - " + Messages.message("port"));
}
}
/**
* {@inheritDoc}
*/
@Override
protected void addPropertyChangeListeners() {
Unit selected = getSelectedUnit();
if (selected != null) {
selected.addPropertyChangeListener(Unit.CARGO_CHANGE, this);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void removePropertyChangeListeners() {
Unit selected = getSelectedUnit();
if (selected != null) {
selected.removePropertyChangeListener(Unit.CARGO_CHANGE, this);
}
}
// extend UnitPanel
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Unit unit) {
return unit.isCarrier();
}
/**
* {@inheritDoc}
*/
@Override
public void selectLabel() {
removePropertyChangeListeners();
super.selectLabel();
addPropertyChangeListeners();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g;
if (selectedUnitLabel != null) {
final Color oldColor = g.getColor();
final Stroke oldStroke = g2d.getStroke();
g2d.setColor(ImageLibrary.getColor("color.carrier.selected"));
final Rectangle bounds = selectedUnitLabel.getBounds();
final int scaledLine = getImageLibrary().scaleInt(2);
g2d.setStroke(new BasicStroke(scaledLine));
g2d.drawRect(bounds.x - scaledLine, bounds.y - scaledLine, bounds.width + scaledLine * 2, bounds.height + scaledLine * 2);
g2d.setColor(oldColor);
g2d.setStroke(oldStroke);
}
}
}
/**
* A panel that holds goods that represent cargo that is inside
* the Colony.
*/
public final class WarehousePanel extends MigPanel
implements DropTarget, PropertyChangeListener {
/**
* Creates a WarehousePanel.
*/
public WarehousePanel() {
super("WarehousePanelUI",
new MigLayout("fill, gap push, insets 0"));
}
/**
* Initialize this WarehousePanel.
*/
public void initialize() {
cleanup();
addPropertyChangeListeners();
update();
}
/**
* Clean up this WarehousePanel.
*/
public void cleanup() {
removePropertyChangeListeners();
removeAll();
}
/**
* Add the property change listeners to this panel.
*/
private void addPropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.getGoodsContainer().addPropertyChangeListener(this);
}
}
/**
* Remove the property change listeners from this panel.
*/
private void removePropertyChangeListeners() {
final Colony colony = getColony();
if (colony != null) {
colony.getGoodsContainer().removePropertyChangeListener(this);
}
}
/**
* Update this WarehousePanel.
*/
public void update() {
final Colony colony = getColony();
if (colony == null) return;
removeAll();
ClientOptions options = getClientOptions();
final int threshold = (FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.MENUS))
? 1
: options.getInteger(ClientOptions.MIN_NUMBER_FOR_DISPLAYING_GOODS);
final Game game = colony.getGame();
final Specification spec = colony.getSpecification();
for (GoodsType goodsType : spec.getStorableGoodsTypeList()) {
int count = colony.getGoodsCount(goodsType);
if (count >= threshold) {
Goods goods = new Goods(game, colony, goodsType, count);
GoodsLabel goodsLabel = new GoodsLabel(getFreeColClient(), goods, true);
if (ColonyPanel.this.isEditable()) {
goodsLabel.setTransferHandler(defaultTransferHandler);
goodsLabel.addMouseListener(pressListener);
}
add(goodsLabel, "alignx center");
}
}
ColonyPanel.this.updateProduction();
revalidate();
repaint();
}
// Interface DropTarget
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Goods goods) {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(GoodsType goodsType) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Unit unit) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Component add(Component comp, boolean editState) {
if (editState) {
if (!(comp instanceof GoodsLabel)) {
logger.warning("Invalid component: " + comp);
return null;
}
comp.getParent().remove(comp);
return comp;
}
return add(comp);
}
/**
* {@inheritDoc}
*/
@Override
public int suggested(GoodsType type) {
Colony colony = getColony();
return colony.getWarehouseCapacity() - colony.getGoodsCount(type);
}
// Interface PropertyChangeListener
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
final Colony colony = getColony();
if (colony != null && event != null) {
logger.finest(colony.getName() + "-warehouse change "
+ event.getPropertyName()
+ ": " + event.getOldValue()
+ " -> " + event.getNewValue());
}
update();
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void removeNotify() {
super.removeNotify();
removeAll();
setLayout(null);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final BufferedImage colonyWarehouseBackground = getImageLibrary().getColonyWarehouseBackground();
ImageUtils.drawTiledImage(colonyWarehouseBackground, g, this, getInsets());
}
}
/**
* This panel is a list of the colony's buildings.
*/
public final class BuildingsPanel extends MigPanel {
/** Always pop up the build queue when clicking on a building. */
private final MouseAdapter buildQueueListener
= new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
getGUI().showBuildQueuePanel(getColony());
}
};
/**
* Creates this BuildingsPanel.
*/
public BuildingsPanel() {
// TODO: Use different seeds per colony for BuildingsLayoutManager?
super("BuildingsPanelUI", new BuildingsLayoutManager());
}
/**
* Initializes the game data in this buildings panel.
*/
public void initialize() {
final Colony colony = getColony();
if (colony == null) return;
cleanup();
final List<BuildingType> allBuildableTypes = getSpecification().getBuildingTypeList().stream()
.filter(bt -> bt.getUpgradesFrom() == null)
.collect(Collectors.toList());
final Random random = new Random(13 * getColony().getTile().getX() + 23 * getColony().getTile().getY());
Collections.shuffle(allBuildableTypes, random);
Map<BuildingType, List<Building>> constructedBuildings = colony.getBuildings().stream().collect(Collectors.groupingBy(g -> g.getType().getFirstLevel()));
for (BuildingType bt : allBuildableTypes) {
if (bt.isDefenceType()) {
continue;
}
if (bt.hasAbility(Ability.PRODUCE_IN_WATER)) {
continue;
}
final List<Building> btBuildings = constructedBuildings.get(bt);
if (btBuildings == null) {
final JPanel emptyPlot = new EmptyBuildingSite();
final Dimension size = getImageLibrary().determineMaxSizeUsingSizeFromAllLevels(bt, colony.getOwner());
emptyPlot.setMinimumSize(size);
emptyPlot.setPreferredSize(size);
emptyPlot.setSize(size);
add(emptyPlot);
} else {
if (btBuildings.size() != 1) {
throw new IllegalStateException("Expected at most one building of each type.");
}
ASingleBuildingPanel aSBP = new ASingleBuildingPanel(btBuildings.get(0));
aSBP.initialize();
add(aSBP);
}
}
update();
}
/**
* Clean up this buildings panel.
*/
public void cleanup() {
for (Component component : getComponents()) {
if (component instanceof ASingleBuildingPanel) {
((ASingleBuildingPanel)component).cleanup();
}
}
removeAll();
}
/**
* Update this buildings panel.
*/
public void update() {
for (Component component : getComponents()) {
if (component instanceof ASingleBuildingPanel) {
((ASingleBuildingPanel)component).update();
}
}
repaint();
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void removeNotify() {
super.removeNotify();
removeAll();
setLayout(null);
}
/**
* This panel is a single line (one building) in the
* {@code BuildingsPanel}.
*/
public final class ASingleBuildingPanel extends BuildingPanel
implements DropTarget {
/**
* Creates this ASingleBuildingPanel.
*
* @param building The {@code Building} to display
* information from.
*/
public ASingleBuildingPanel(Building building) {
super(getFreeColClient(), building);
setOpaque(false);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize() {
if (ColonyPanel.this.isEditable()) {
super.initialize();
addMouseListener(releaseListener);
if (getBuilding().hasAbility(Ability.BUILD)) {
addMouseListener(buildQueueListener);
} else {
addMouseListener(frameMoveDispatchListener);
addMouseMotionListener(frameMoveDispatchListener);
}
setTransferHandler(defaultTransferHandler);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void cleanup() {
super.cleanup();
removeMouseListener(releaseListener);
removeMouseListener(buildQueueListener);
removeMouseListener(frameMoveDispatchListener);
removeMouseMotionListener(frameMoveDispatchListener);
setTransferHandler(null);
removeAll();
}
/**
* {@inheritDoc}
*/
@Override
public void update() {
super.update();
if (ColonyPanel.this.isEditable()) {
for (UnitLabel unitLabel : getUnitLabels()) {
unitLabel.setTransferHandler(defaultTransferHandler);
unitLabel.addMouseListener(pressListener);
}
}
}
/**
* Try to assign a unit to work this building.
*
* @param unit The {@code Unit} to try.
* @return True if the work begins.
*/
private boolean tryWork(Unit unit) {
Building building = getBuilding();
NoAddReason reason = building.getNoAddReason(unit);
if (reason != NoAddReason.NONE) {
getGUI().showInformationPanel(building, reason.getDescriptionKey());
return false;
}
return ColonyPanel.this.tryWork(unit, building);
}
// Interface DropTarget
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Goods goods) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(GoodsType goodsType) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Unit unit) {
return unit.isPerson();
}
/**
* {@inheritDoc}
*/
@Override
public Component add(Component comp, boolean editState) {
if (editState) {
if (comp instanceof UnitLabel) {
if (!tryWork(((UnitLabel)comp).getUnit())) return null;
} else {
logger.warning("An invalid component was dropped"
+ " on this ASingleBuildingPanel.");
return null;
}
update();
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public int suggested(GoodsType type) { return -1; } // N/A
// Interface PropertyChangeListener
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
ColonyPanel.this.updateProduction();
}
}
public final class EmptyBuildingSite extends JPanel{
EmptyBuildingSite() {
setOpaque(false);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final ImageLibrary lib = getImageLibrary();
final BufferedImage image = lib.getScaledBuildingEmptyLandImage();
final Dimension size = getSize();
g.drawImage(image,
(size.width - image.getWidth()) / 2,
size.height - image.getHeight(),
this);
}
}
}
private int tilesPanelTopOffset(final Dimension tileSize) {
/*
* TODO: tileSize.height / 2 is needed to avoid clipping mountains,
* but that takes a lot of space. Perhaps make the tile render
* above the border (and clip when the panel is displayed
* without them)?
*/
final int topOffset = tileSize.height / 4;
return topOffset;
}
/**
* A panel that displays the tiles in the immediate area around the colony.
* TilesPanel class must use the ImageLibrary inside SwingGUI.tileMapViewer
* for everything.
*/
public final class TilesPanel extends JPanel {
/** The tiles around the colony. */
private final Tile[][] tiles = new Tile[3][3];
/** A currently displayed production message. */
private FreeColPanel cachedPanel = null;
/** The work location that would be better to produce with. */
private WorkLocation bestLocation = null;
/**
* Creates a TilesPanel.
*/
public TilesPanel() {
setBackground(Color.BLACK);
setBorder(null);
setLayout(null);
setOpaque(false);
}
/**
* Initialize the game data in this tiles panel.
*/
public void initialize() {
final Colony colony = getColony();
if (colony == null) return;
cleanup();
Tile tile = colony.getTile();
tiles[0][0] = tile.getNeighbourOrNull(Direction.N);
tiles[0][1] = tile.getNeighbourOrNull(Direction.NE);
tiles[0][2] = tile.getNeighbourOrNull(Direction.E);
tiles[1][0] = tile.getNeighbourOrNull(Direction.NW);
tiles[1][1] = tile;
tiles[1][2] = tile.getNeighbourOrNull(Direction.SE);
tiles[2][0] = tile.getNeighbourOrNull(Direction.W);
tiles[2][1] = tile.getNeighbourOrNull(Direction.SW);
tiles[2][2] = tile.getNeighbourOrNull(Direction.S);
int layer = 2;
for (int x = 0; x < tiles.length; x++) {
for (int y = 0; y < tiles[x].length; y++) {
if (tiles[x][y] == null) {
logger.warning("Null tile for " + getColony()
+ " at " + x + "," + y);
continue;
}
ColonyTile colonyTile = colony.getColonyTile(tiles[x][y]);
if (colonyTile == null) {
logger.warning("Null colony tile for " + getColony()
+ " on " + tiles[x][y] + " at " + x + "," + y);
dump("TILES", colony.getColonyTiles());
continue;
}
ASingleTilePanel aSTP
= new ASingleTilePanel(colonyTile, x, y);
aSTP.initialize();
add(aSTP, Integer.valueOf(layer++));
}
}
update();
}
/**
* Clean up this tiles panel.
*/
public void cleanup() {
for (Component component : getComponents()) {
if (component instanceof ASingleTilePanel) {
((ASingleTilePanel)component).cleanup();
}
}
removeAll();
}
/**
* Update this tiles panel.
*/
public void update() {
for (Component component : getComponents()) {
if (component instanceof ASingleTilePanel) {
((ASingleTilePanel)component).update();
}
}
repaint();
}
/**
* Display the poor production message.
*
* @param best The better work location.
* @param template The {@code StringTemplate} with the message.
*/
public void showPoorProduction(WorkLocation best,
StringTemplate template) {
// Already warned about this? Do nothing if so.
if (this.bestLocation == best) return;
if (this.cachedPanel != null) {
getGUI().removeComponent(this.cachedPanel);
}
this.cachedPanel = getGUI().showInformationPanel(best, template);
this.bestLocation = best;
}
// Override JComponent
/**
* {@inheritDoc}
*/
@Override
public void paintComponent(Graphics g) {
final Colony colony = getColony();
if (colony != null) {
final Dimension size = getImageLibrary().getTileSize();
final int topOffset = tilesPanelTopOffset(size);
g.translate(0, topOffset);
getGUI().displayColonyTiles((Graphics2D)g, tiles, colony);
g.translate(0, -topOffset);
}
}
/**
* Panel for visualizing a {@code ColonyTile}. The
* component itself is not visible, however the content of the
* component is (i.e. the people working and the production)
*/
public final class ASingleTilePanel extends JPanel
implements DropTarget, PropertyChangeListener {
/** The colony tile to monitor. */
private final ColonyTile colonyTile;
/**
* Create a new single tile panel.
*
* @param colonyTile The {@code ColonyTile} to monitor.
* @param x The x offset.
* @param y The y offset.
*/
public ASingleTilePanel(ColonyTile colonyTile, int x, int y) {
this.colonyTile = colonyTile;
setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
setOpaque(false);
// Size and position:
Dimension size = getImageLibrary().getTileSize();
final int topOffset = tilesPanelTopOffset(size);
setSize(size);
setLocation(((2 - x) + y) * size.width / 2,
(x + y) * size.height / 2 + topOffset);
}
/**
* Initialize this single tile panel.
*/
public void initialize() {
if (ColonyPanel.this.isEditable()) {
cleanup();
addMouseListener(pressListener);
addMouseListener(releaseListener);
setTransferHandler(defaultTransferHandler);
addPropertyChangeListeners();
}
}
/**
* Clean up this single tile panel.
*/
public void cleanup() {
removeMouseListener(pressListener);
removeMouseListener(releaseListener);
setTransferHandler(null);
removePropertyChangeListeners();
removeAll();
}
/**
* Adds PropertyChangeListeners
*/
private void addPropertyChangeListeners() {
if (this.colonyTile != null) {
this.colonyTile.addPropertyChangeListener(this);
}
}
/**
* Removes PropertyChangeListeners
*/
private void removePropertyChangeListeners() {
if (this.colonyTile != null) {
this.colonyTile.removePropertyChangeListener(this);
}
}
/**
* Update this single tile panel.
*/
public void update() {
removeAll();
if (this.colonyTile == null) {
logger.warning("Update of " + getColony()
+ " null colony tile.");
return;
}
final FreeColClient fcc = getFreeColClient();
UnitLabel label = null;
for (Unit unit : this.colonyTile.getUnitList()) {
label = new UnitLabel(fcc, unit, false, false);
if (ColonyPanel.this.isEditable()) {
label.setTransferHandler(defaultTransferHandler);
label.addMouseListener(pressListener);
}
super.add(label);
}
updateDescriptionLabel(label);
if (this.colonyTile.isColonyCenterTile()) {
setLayout(new GridLayout(2, 1));
ProductionInfo info
= colony.getProductionInfo(this.colonyTile);
if (info != null) {
for (AbstractGoods ag : info.getProduction()) {
ProductionLabel productionLabel
= new ProductionLabel(fcc, ag);
productionLabel.addMouseListener(pressListener);
add(productionLabel);
}
}
}
}
/**
* Gets the colony tile this panel is handling.
*
* @return The colony tile.
*/
public ColonyTile getColonyTile() {
return this.colonyTile;
}
/**
* Updates the description label, which is a tooltip with
* the terrain type, road and plow indicator, if any.
*
* If a unit is on it update the tooltip of it instead.
*
* @param unitLabel The {@code UnitLabel} to update.
*/
private void updateDescriptionLabel(UnitLabel unitLabel) {
String tileMsg = Messages.message(this.colonyTile.getLabel());
if (unitLabel == null) {
setToolTipText(tileMsg);
} else {
final Unit unit = unitLabel.getUnit();
unitLabel.setDescriptionLabel(tileMsg + " ["
+ unit.getDescription(Unit.UnitLabelType.NATIONAL)
+ "]");
}
}
/**
* Try to work this tile with a specified unit.
*
* @param unit The {@code Unit} to work the tile.
* @return True if the unit succeeds.
*/
private boolean tryWork(Unit unit) {
final Colony colony = getColony();
Tile tile = this.colonyTile.getWorkTile();
Player player = unit.getOwner();
if (tile.getOwningSettlement() != colony) {
// Need to acquire the tile before working it.
NoClaimReason claim
= player.canClaimForSettlementReason(tile);
switch (claim) {
case NONE: case NATIVES:
if (igc().claimTile(tile, colony)
&& tile.getOwningSettlement() == colony) {
logger.info("Colony " + colony.getName()
+ " claims tile " + tile
+ " with unit " + unit.getId());
} else {
logger.warning("Colony " + colony.getName()
+ " did not claim " + tile
+ " with unit " + unit.getId());
return false;
}
break;
default: // Otherwise, can not use land
getGUI().showInformationPanel(tile, claim.getDescriptionKey());
return false;
}
// Check reason again, claim should be satisfied.
if (tile.getOwningSettlement() != colony) {
throw new RuntimeException("Claim failed: " + claim);
}
}
// Claim sorted, but complain about other failure.
NoAddReason reason = this.colonyTile.getNoAddReason(unit);
if (reason != NoAddReason.NONE) {
getGUI().showInformationPanel(this.colonyTile,
StringTemplate.template(reason.getDescriptionKey()));
return false;
}
if (!ColonyPanel.this.tryWork(unit, this.colonyTile)) return false;
GoodsType workType = unit.getWorkType();
if (workType != null
&& getClientOptions().getBoolean(ClientOptions.SHOW_NOT_BEST_TILE)) {
WorkLocation best = colony.getWorkLocationFor(unit,
workType);
if (best != null && this.colonyTile != best
&& (this.colonyTile.getPotentialProduction(workType, unit.getType())
< best.getPotentialProduction(workType, unit.getType()))) {
StringTemplate template = StringTemplate
.template("colonyPanel.notBestTile")
.addStringTemplate("%unit%",
unit.getLabel(Unit.UnitLabelType.NATIONAL))
.addNamed("%goods%", workType)
.addStringTemplate("%tile%", best.getLabel());
showPoorProduction(best, template);
}
}
return true;
}
// Interface DropTarget
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(Unit unit) {
return unit.isPerson();
}
/**
* {@inheritDoc}
*/
@Override
public Component add(Component comp, boolean editState) {
if (editState) {
if (comp instanceof UnitLabel) {
UnitLabel label = (UnitLabel)comp;
if (!tryWork(label.getUnit())) return null;
label.setSmall(false);
} else {
logger.warning("An invalid component was dropped"
+ " on this ASingleTilePanel.");
return null;
}
}
update();
return comp;
}
// Interface PropertyChangeListener
/**
* {@inheritDoc}
*
* Updates the production value via {@link #updateProduction()}
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
String property = event.getPropertyName();
logger.finest(this.colonyTile.getId() + " change " + property
+ ": " + event.getOldValue()
+ " -> " + event.getNewValue());
ColonyPanel.this.updateProduction();
}
// Override JComponent
/**
* Checks if this {@code JComponent} contains the given
* coordinate.
*
* @param px The x coordinate to check.
* @param py The y coordinate to check.
* @return true if the coordinate is inside.
*/
@Override
public boolean contains(int px, int py) {
int w = getWidth();
int h = getHeight();
int dx = Math.abs(w/2 - px);
int dy = Math.abs(h/2 - py);
return (dx + w * dy / h) <= w/2;
}
}
}
private final MouseAdapter frameMoveDispatchListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mousePressed(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseReleased(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseEntered(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseExited(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseDragged(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
@Override
public void mouseMoved(MouseEvent e) {
dispatchTo(e, ColonyPanel.this);
}
private void dispatchTo(MouseEvent e, Component target){
final Component source = (Component) e.getSource();
MouseEvent targetEvent = SwingUtilities.convertMouseEvent(source, e, target);
target.dispatchEvent(targetEvent);
}
};
}