freecol/src/net/sf/freecol/client/gui/label/UnitLabel.java

454 lines
15 KiB
Java

/**
* Copyright (C) 2002-2021 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.label;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.InGameController;
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.panel.CargoPanel;
import net.sf.freecol.client.gui.panel.ColonyPanel;
import net.sf.freecol.client.gui.panel.EuropePanel;
import net.sf.freecol.client.gui.panel.InPortPanel;
import net.sf.freecol.client.gui.panel.report.ReportPanel;
import net.sf.freecol.client.gui.panel.Utility;
import net.sf.freecol.client.gui.Size;
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.Building;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.WorkLocation;
import static net.sf.freecol.common.model.Unit.*;
import static net.sf.freecol.common.util.StringUtils.*;
/**
* This label holds Unit data in addition to the JLabel data, which makes it
* ideal to use for drag and drop purposes.
*/
public final class UnitLabel extends FreeColLabel
implements ActionListener, CargoLabel, Draggable {
private static final Logger logger = Logger.getLogger(UnitLabel.class.getName());
/** The different actions a {@code Unit} is allowed to take. */
public enum UnitAction {
ASSIGN,
CLEAR_SPECIALITY,
ACTIVATE_UNIT,
FORTIFY,
SENTRY,
COLOPEDIA,
LEAVE_TOWN,
WORK_COLONYTILE, // Must match the WorkLocation actual type
WORK_BUILDING, // Must match the WorkLocation actual type
CLEAR_ORDERS,
ASSIGN_TRADE_ROUTE,
LEAVE_SHIP,
UNLOAD,
}
/** The enclosing client. */
private final FreeColClient freeColClient;
/** The unit this is a label for. */
private final Unit unit;
/** Is this a currently selected unit? */
private boolean selected;
/** Is this a small label? */
private boolean isSmall;
/**
* Should the location information be ignored for this label
* description?
*/
private boolean ignoreLocation;
/** Font for labels. */
private final Font tinyFont;
/**
* Creates a JLabel to display a unit.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param unit The {@code Unit} to display.
*/
public UnitLabel(FreeColClient freeColClient, Unit unit) {
this(freeColClient, unit, false);
}
/**
* Creates a JLabel to display a unit.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param unit The {@code Unit} to display.
* @param isSmall The image will be smaller if set to {@code true}.
*/
public UnitLabel(FreeColClient freeColClient, Unit unit, boolean isSmall) {
this(freeColClient, unit, isSmall, false);
}
/**
* Creates a JLabel to display a unit.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param unit The {@code Unit} to display.
* @param isSmall The image will be smaller if set to {@code true}.
* @param ignoreLocation The image will not include production or state
* information if set to {@code true}.
*/
public UnitLabel(FreeColClient freeColClient, Unit unit,
boolean isSmall, boolean ignoreLocation) {
this.freeColClient = freeColClient;
this.unit = unit;
this.selected = false;
this.isSmall = isSmall;
this.ignoreLocation = ignoreLocation;
this.tinyFont = FontLibrary.getUnscaledFont("normal-plain-tiny");
updateIcon();
}
/**
* Internal GUI accessor.
*
* @return The current {@code GUI}.
*/
private GUI getGUI() {
return this.freeColClient.getGUI();
}
/**
* Get the correct image library for this unit label.
*
* @return The {@code ImageLibrary} to use.
*/
private ImageLibrary getImageLibrary() {
return getGUI().getFixedImageLibrary();
}
/**
* Get the associated unit.
*
* @return The {@code Unit} this is the label for.
*/
public Unit getUnit() {
return this.unit;
}
/**
* Sets whether or not this unit should be selected.
*
* @param b Whether or not this unit should be selected.
*/
public void setSelected(boolean b) {
this.selected = b;
}
/**
* Makes a smaller version.
*
* @param isSmall The image will be smaller if set to {@code true}.
*/
public void setSmall(final boolean isSmall) {
final ImageLibrary lib = getImageLibrary();
if (isSmall) {
setPreferredSize(null);
setIcon(new ImageIcon(lib.getSmallUnitImage(this.unit)));
setBorder(Utility.blankBorder(0, 2, 0, 0));
} else {
Icon imageIcon = new ImageIcon(lib.getScaledUnitImage(this.unit));
if (this.unit.getLocation() instanceof ColonyTile) {
Dimension tileSize = lib.scale(ImageLibrary.TILE_SIZE);
tileSize.width /= 2;
tileSize.height = imageIcon.getIconHeight();
setSize(tileSize);
} else {
setPreferredSize(null);
}
setIcon(imageIcon);
setBorder((this.unit.getLocation() instanceof ColonyTile)
? Utility.blankBorder(0, 15, 0, 15)
: Utility.blankBorder(0, 5, 0, 5));
}
this.isSmall = isSmall;
}
/**
* Sets the description label.
*
* The description label is a tooltip with the unit name and description of
* the terrain its on if applicable
*
* @param label The string to set the label to.
*/
public void setDescriptionLabel(String label) {
setToolTipText(label);
}
/**
* Update the icon for this unit label.
*/
public void updateIcon() {
setDescriptionLabel(getUnit().getDescription(UnitLabelType.FULL));
setSmall(this.isSmall);
// repaint(0, 0, getWidth(), getHeight());
// uc.refresh();
}
// Interface CargoLabel
/**
* {@inheritDoc}
*/
@Override
public boolean addCargo(Component comp, Unit carrier, CargoPanel cargoPanel) {
final Unit u = ((UnitLabel)comp).getUnit();
if (carrier.canAdd(u)) {
Container oldParent = comp.getParent();
if (cargoPanel.igc().boardShip(u, carrier)) {
((UnitLabel)comp).setSmall(false);
if (oldParent != null) oldParent.remove(comp);
cargoPanel.update();
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void removeCargo(Component comp, CargoPanel cargoPanel) {
final Unit u = ((UnitLabel)comp).getUnit();
cargoPanel.igc().leaveShip(u);
cargoPanel.update();
}
// Interface Draggable
/**
* {@inheritDoc}
*/
@Override
public boolean isOnCarrier() {
return this.unit != null && this.unit.isOnCarrier();
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
final Game game = this.freeColClient.getGame();
final Specification spec = game.getSpecification();
final InGameController igc = this.freeColClient.getInGameController();
String[] args = ae.getActionCommand().split("/");
GoodsType gt;
switch (Enum.valueOf(UnitAction.class, upCase(args[0]))) {
case ASSIGN:
igc.assignTeacher(this.unit,
game.getFreeColGameObject(args[1], Unit.class));
break;
case WORK_COLONYTILE:
if (args.length < 3) break;
ColonyTile colonyTile
= game.getFreeColGameObject(args[1], ColonyTile.class);
if (args.length >= 4 && "!".equals(args[3])) {
// Claim tile if needed
if (!igc.claimTile(colonyTile.getWorkTile(),
this.unit.getColony())) break;
}
if (colonyTile != this.unit.getLocation()) {
igc.work(this.unit, colonyTile);
}
if ((gt = spec.getGoodsType(args[2])) != null
&& this.unit.getWorkType() != gt) {
igc.changeWorkType(this.unit, gt);
}
break;
case WORK_BUILDING:
if (args.length < 3) break;
Building building
= game.getFreeColGameObject(args[1], Building.class);
if (building != this.unit.getLocation()) {
igc.work(this.unit, building);
}
if ((gt = spec.getGoodsType(args[2])) != null
&& this.unit.getWorkType() != gt) {
igc.changeWorkType(this.unit, gt);
}
break;
case ACTIVATE_UNIT:
igc.changeState(this.unit, UnitState.ACTIVE);
getGUI().changeView(this.unit, false);
break;
case FORTIFY:
igc.changeState(this.unit, UnitState.FORTIFYING);
break;
case SENTRY:
igc.changeState(this.unit, UnitState.SENTRY);
break;
case COLOPEDIA:
getGUI().showColopediaPanel(this.unit.getType().getId());
break;
case LEAVE_TOWN:
igc.putOutsideColony(this.unit);
break;
case CLEAR_SPECIALITY:
igc.clearSpeciality(this.unit);
break;
case CLEAR_ORDERS:
igc.clearOrders(this.unit);
break;
case ASSIGN_TRADE_ROUTE:
getGUI().showTradeRoutePanel(this.unit);
break;
case LEAVE_SHIP:
igc.leaveShip(this.unit);
break;
case UNLOAD:
igc.unload(this.unit);
break;
}
updateIcon();
}
// Override JComponent
/**
* {@inheritDoc}
*/
@Override
public void paintComponent(Graphics g) {
final Player player = this.freeColClient.getMyPlayer();
final ImageLibrary lib = getImageLibrary();
if (ignoreLocation || selected
|| (!this.unit.isCarrier()
&& this.unit.getState() != UnitState.SENTRY)) {
setEnabled(true);
} else if (!player.owns(this.unit) && this.unit.getColony() == null) {
setEnabled(true);
} else {
setEnabled(false);
}
super.paintComponent(g);
if (ignoreLocation) return;
if (this.unit.getLocation() instanceof ColonyTile) {
// Not Buildings. Buildings have their own production display.
GoodsType workType = this.unit.getWorkType();
if (workType != null) {
int production = ((ColonyTile)this.unit.getLocation())
.getTotalProductionOf(workType);
ProductionLabel pl = new ProductionLabel(this.freeColClient,
new AbstractGoods(workType, production));
g.translate(0, 10);
pl.paintComponent(g);
g.translate(0, -10);
}
} else if (getParent() instanceof ColonyPanel.OutsideColonyPanel ||
getParent() instanceof InPortPanel ||
getParent() instanceof EuropePanel.EuropeanDocksPanel ||
getParent().getParent() instanceof ReportPanel) {
String text = Messages.message(this.unit.getOccupationLabel(player, false));
g.drawImage(lib.getOccupationIndicatorChip((Graphics2D)g, this.unit, text), 0, 0, null);
if (this.unit.isDamaged()) {
String underRepair = Messages.message(this.unit.getRepairLabel());
int idx = underRepair.indexOf('(');
String underRepair1 = underRepair.substring(0, idx).trim();
String underRepair2 = underRepair.substring(idx).trim();
Image repairImage1 = lib.getStringImage(g, underRepair1,
Color.RED, this.tinyFont);
Image repairImage2 = lib.getStringImage(g, underRepair2,
Color.RED, this.tinyFont);
int textHeight = repairImage1.getHeight(null) + repairImage2.getHeight(null);
int leftIndent = Math.min(5, Math.min(getWidth() - repairImage1.getWidth(null),
getWidth() - repairImage2.getWidth(null)));
g.drawImage(repairImage1,
leftIndent, // indent from left side of label (icon is placed at the left side)
((getHeight() - textHeight) / 2),
null);
g.drawImage(repairImage2,
leftIndent,
((getHeight() - textHeight) / 2) + repairImage1.getHeight(null),
null);
}
}
}
// Override Component
/**
* {@inheritDoc}
*/
@Override
public void setEnabled(boolean b) {
if (!b) {
// Grayscale images are slow to compute, only bother in the
// rare case we need them and let the image cache keep it.
ImageLibrary lib = getImageLibrary();
Icon disabledImageIcon = (this.isSmall)
? new ImageIcon(lib.getSmallUnitImage(this.unit, true))
: new ImageIcon(lib.getScaledUnitImage(this.unit, true));
setDisabledIcon(disabledImageIcon);
}
super.setEnabled(b);
}
}