package games.strategy.triplea.ui; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Optional; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.RepairRule; import games.strategy.engine.data.Resource; import games.strategy.engine.data.ResourceCollection; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.triplea.Constants; import games.strategy.triplea.TripleAUnit; import games.strategy.triplea.attachments.TechAbilityAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.ui.ScrollableTextField; import games.strategy.ui.ScrollableTextFieldListener; import games.strategy.ui.SwingAction; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.IntegerMap; import games.strategy.util.Match; public class ProductionRepairPanel extends JPanel { private static final long serialVersionUID = -6344711064699083729L; private final JFrame m_owner = null; private JDialog m_dialog; private final IUIContext m_uiContext; private final List<Rule> m_rules = new ArrayList<>(); private final JLabel m_left = new JLabel(); private JButton m_done; private PlayerID m_id; private boolean m_bid; private Collection<PlayerID> m_allowedPlayersToRepair; private GameData m_data; private static HashMap<Unit, Integer> m_repairCount = new HashMap<>(); public static HashMap<Unit, IntegerMap<RepairRule>> getProduction(final PlayerID id, final Collection<PlayerID> allowedPlayersToRepair, final JFrame parent, final GameData data, final boolean bid, final HashMap<Unit, IntegerMap<RepairRule>> initialPurchase, final IUIContext uiContext) { return new ProductionRepairPanel(uiContext).show(id, allowedPlayersToRepair, parent, data, bid, initialPurchase); } /** * Shows the production panel, and returns a map of selected rules. */ public HashMap<Unit, IntegerMap<RepairRule>> show(final PlayerID id, final Collection<PlayerID> allowedPlayersToRepair, final JFrame parent, final GameData data, final boolean bid, final HashMap<Unit, IntegerMap<RepairRule>> initialPurchase) { if (!(parent == m_owner)) { m_dialog = null; } if (m_dialog == null) { initDialog(parent); } this.m_bid = bid; this.m_allowedPlayersToRepair = allowedPlayersToRepair; this.m_data = data; this.initRules(id, allowedPlayersToRepair, data, initialPurchase); this.initLayout(); this.calculateLimits(); m_dialog.pack(); m_dialog.setLocationRelativeTo(parent); SwingUtilities.invokeLater(() -> m_done.requestFocusInWindow()); m_dialog.setVisible(true); m_dialog.dispose(); return getProduction(); } // this method can be accessed by subclasses public List<Rule> getRules() { return this.m_rules; } public static HashMap<Unit, Integer> getUnitRepairs() { return m_repairCount; } private void initDialog(final JFrame root) { m_dialog = new JDialog(root, "Repair", true); m_dialog.getContentPane().add(this); final Action closeAction = SwingAction.of("", e -> m_dialog.setVisible(false)); // close the window on escape // this is mostly for developers, makes it much easier to quickly cycle through steps final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); final String key = "production.panel.close.prod.popup"; m_dialog.getRootPane().getActionMap().put(key, closeAction); m_dialog.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(stroke, key); } /** Creates new ProductionRepairPanel. */ // the constructor can be accessed by subclasses public ProductionRepairPanel(final IUIContext uiContext) { m_uiContext = uiContext; } private void initRules(final PlayerID player, final Collection<PlayerID> allowedPlayersToRepair, final GameData data, final HashMap<Unit, IntegerMap<RepairRule>> initialPurchase) { if (!games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) { return; } m_data.acquireReadLock(); try { this.m_id = player; this.m_allowedPlayersToRepair = allowedPlayersToRepair; final CompositeMatchAnd<Unit> myDamagedUnits = new CompositeMatchAnd<>(Matches.unitIsOwnedByOfAnyOfThesePlayers(m_allowedPlayersToRepair), Matches.UnitHasTakenSomeBombingUnitDamage); final Collection<Territory> terrsWithPotentiallyDamagedUnits = Match.getMatches(data.getMap().getTerritories(), Matches.territoryHasUnitsThatMatch(myDamagedUnits)); for (final RepairRule repairRule : player.getRepairFrontier()) { for (final Territory terr : terrsWithPotentiallyDamagedUnits) { for (final Unit u : Match.getMatches(terr.getUnits().getUnits(), myDamagedUnits)) { if (!repairRule.getResults().keySet().iterator().next().equals(u.getType())) { continue; } final TripleAUnit taUnit = (TripleAUnit) u; final Rule rule = new Rule(repairRule, player, m_uiContext, u, terr); // int initialQuantity = initialPurchase.getInt(repairRule); int initialQuantity = 0; if (initialPurchase.get(u) != null) { initialQuantity = initialPurchase.get(u).getInt(repairRule); } rule.setQuantity(initialQuantity); rule.setMax(taUnit.getHowMuchCanThisUnitBeRepaired(u, terr)); rule.setName(u.toString()); m_rules.add(rule); } } } } finally { m_data.releaseReadLock(); } } private void initLayout() { final Insets nullInsets = new Insets(0, 0, 0, 0); this.removeAll(); this.setLayout(new GridBagLayout()); final JLabel legendLabel = new JLabel("Repair Units"); add(legendLabel, new GridBagConstraints(0, 0, 30, 1, 1, 1, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 0), 0, 0)); for (int x = 0; x < m_rules.size(); x++) { final boolean even = (x / 2) * 2 == x; add(m_rules.get(x), new GridBagConstraints(x / 2, even ? 1 : 2, 1, 1, 1, 1, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, nullInsets, 0, 0)); } add(m_left, new GridBagConstraints(0, 3, 30, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(8, 8, 0, 12), 0, 0)); m_done = new JButton(m_done_action); add(m_done, new GridBagConstraints(0, 4, 30, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 8, 0), 0, 0)); } protected void setLeft(final ResourceCollection left) { final ResourceCollection total = getResources(); m_left.setText("<html>You have " + left + " left.<br>Out of " + total + "</html>"); } Action m_done_action = SwingAction.of("Done", e -> m_dialog.setVisible(false)); private HashMap<Unit, IntegerMap<RepairRule>> getProduction() { final HashMap<Unit, IntegerMap<RepairRule>> prod = new HashMap<>(); // IntegerMap<RepairRule> repairRule = new IntegerMap<RepairRule>(); for (final Rule rule : m_rules) { final int quantity = rule.getQuantity(); if (quantity != 0) { final IntegerMap<RepairRule> repairRule = new IntegerMap<>(); final Unit unit = rule.getUnit(); repairRule.put(rule.getProductionRule(), quantity); prod.put(unit, repairRule); } } return prod; } protected void calculateLimits() { // final IntegerMap<Resource> cost; final ResourceCollection resources = getResources(); final ResourceCollection spent = new ResourceCollection(m_data); for (final Rule current : m_rules) { spent.add(current.getCost(), current.getQuantity()); } final double discount = TechAbilityAttachment.getRepairDiscount(m_id, m_data); if (discount != 1.0D) { spent.discount(discount); } final ResourceCollection leftToSpend = resources.difference(spent); setLeft(leftToSpend); for (final Rule current : m_rules) { int max = leftToSpend.fitsHowOften(current.getCost()); if (discount != 1.0F) { max = (int) (max / discount); } max += current.getQuantity(); current.setMax(max); } } private ResourceCollection getResources() { if (m_bid) { // TODO bid only allows you to add PU's to the bid... maybe upgrading Bids so multiple resources can be given? // (actually, bids should // not cover repairing at all...) final String propertyName = m_id.getName() + " bid"; final int bid = m_data.getProperties().get(propertyName, 0); final ResourceCollection bidCollection = new ResourceCollection(m_data); m_data.acquireReadLock(); try { bidCollection.addResource(m_data.getResourceList().getResource(Constants.PUS), bid); } finally { m_data.releaseReadLock(); } return bidCollection; } else { return m_id.getResources(); } } public class Rule extends JPanel { private static final long serialVersionUID = -6781214135310064908L; private final ScrollableTextField m_text = new ScrollableTextField(0, Integer.MAX_VALUE); private final IntegerMap<Resource> m_cost; private final RepairRule m_rule; private final Unit m_unit; private final int m_maxRepairAmount; private final int m_repairResults; Rule(final RepairRule rule, final PlayerID id, final IUIContext uiContext, final Unit repairUnit, final Territory territoryUnitIsIn) { setLayout(new GridBagLayout()); m_unit = repairUnit; m_rule = rule; m_cost = rule.getCosts(); final UnitType type = (UnitType) rule.getResults().keySet().iterator().next(); if (!type.equals(repairUnit.getType())) { throw new IllegalStateException("Rule unit type " + type.getName() + " does not match " + repairUnit.toString() + ". Please make sure your maps are up to date!"); } m_repairResults = rule.getResults().getInt(type); final TripleAUnit taUnit = (TripleAUnit) repairUnit; final Optional<ImageIcon> icon = m_uiContext.getUnitImageFactory().getIcon(type, id, m_data, Matches.UnitHasTakenSomeBombingUnitDamage.match(repairUnit), Matches.UnitIsDisabled.match(repairUnit)); final String text = "<html> x " + ResourceCollection.toStringForHTML(m_cost, m_data) + "</html>"; final JLabel label = icon.isPresent() ? new JLabel(text, icon.get(), SwingConstants.LEFT) : new JLabel(text, SwingConstants.LEFT); final JLabel info = new JLabel(territoryUnitIsIn.getName()); m_maxRepairAmount = taUnit.getHowMuchCanThisUnitBeRepaired(repairUnit, territoryUnitIsIn); final JLabel remaining = new JLabel("Damage left to repair: " + m_maxRepairAmount); final int space = 8; this.add(new JLabel(type.getName()), new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2, 0, 0, 0), 0, 0)); this.add(label, new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, space, space, space), 0, 0)); this.add(info, new GridBagConstraints(0, 2, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, space, space, space), 0, 0)); this.add(remaining, new GridBagConstraints(0, 3, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, space, space, space), 0, 0)); this.add(m_text, new GridBagConstraints(0, 4, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(10, space, space, space), 0, 0)); m_text.addChangeListener(m_listener); setBorder(new EtchedBorder()); } public int getRepairResults() { return m_repairResults; } IntegerMap<Resource> getCost() { return m_cost; } public int getQuantity() { return m_text.getValue(); } void setQuantity(final int quantity) { m_text.setValue(quantity); } RepairRule getProductionRule() { return m_rule; } void setMax(final int max) { m_text.setMax((int) (Math.ceil(((double) Math.min(max, m_maxRepairAmount) / (double) m_repairResults)))); } public Unit getUnit() { return m_unit; } } private final ScrollableTextFieldListener m_listener = stf -> calculateLimits(); }