package games.strategy.triplea.ui; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; import games.strategy.engine.data.GameData; import games.strategy.engine.data.NamedAttachable; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.ProductionRule; import games.strategy.engine.data.Resource; import games.strategy.engine.data.ResourceCollection; import games.strategy.engine.data.UnitType; import games.strategy.engine.data.util.ResourceCollectionUtils; import games.strategy.triplea.Constants; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.ui.ScrollableTextField; import games.strategy.ui.ScrollableTextFieldListener; import games.strategy.ui.SwingAction; import games.strategy.ui.SwingComponents; import games.strategy.util.IntegerMap; public class ProductionPanel extends JPanel { private static final long serialVersionUID = -1539053979479586609L; protected final IUIContext m_uiContext; protected List<Rule> m_rules = new ArrayList<>(); protected JLabel m_left = new JLabel(); protected JButton m_done; protected PlayerID m_id; protected GameData m_data; private JDialog m_dialog; private boolean m_bid; public static IntegerMap<ProductionRule> getProduction(final PlayerID id, final JFrame parent, final GameData data, final boolean bid, final IntegerMap<ProductionRule> initialPurchase, final IUIContext context) { return new ProductionPanel(context).show(id, parent, data, bid, initialPurchase); } protected ProductionPanel(final IUIContext uiContext) { m_uiContext = uiContext; } /** * Shows the production panel, and returns a map of selected rules. */ public IntegerMap<ProductionRule> show(final PlayerID id, final JFrame parent, final GameData data, final boolean bid, final IntegerMap<ProductionRule> initialPurchase) { if (parent != null) { final String title = "Produce"; final JPanel contents = this; m_dialog = SwingComponents.newJDialogModal(parent, title, contents); } this.m_bid = bid; this.m_data = data; this.initRules(id, data, initialPurchase); this.initLayout(); this.calculateLimits(); m_dialog.pack(); m_dialog.setLocationRelativeTo(parent); SwingUtilities.invokeLater(() -> m_done.requestFocusInWindow()); // making the dialog visible will block until it is closed m_dialog.setVisible(true); m_dialog.dispose(); return getProduction(); } // this method can be accessed by subclasses protected List<Rule> getRules() { return m_rules; } // made this protected so can be extended by edit production panel protected void initRules(final PlayerID player, final GameData data, final IntegerMap<ProductionRule> initialPurchase) { m_data.acquireReadLock(); try { m_id = player; for (final ProductionRule productionRule : player.getProductionFrontier()) { final Rule rule = new Rule(productionRule, player); final int initialQuantity = initialPurchase.getInt(productionRule); rule.setQuantity(initialQuantity); m_rules.add(rule); } } finally { m_data.releaseReadLock(); } } protected void initLayout() { final Insets nullInsets = new Insets(0, 0, 0, 0); this.removeAll(); this.setLayout(new GridBagLayout()); final JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); final JLabel legendLabel = new JLabel(String.format( "<html>Attack/Defense/Movement.            " + "(Total Resources: %s)</html>", ResourceCollectionUtils.getProductionResources(getResources()))); this.add(legendLabel, new GridBagConstraints(0, 0, 30, 1, 1, 1, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 0), 0, 0)); int rows = m_rules.size() / 7; rows = Math.max(2, rows); for (int x = 0; x < m_rules.size(); x++) { panel.add(m_rules.get(x).getPanelComponent(), new GridBagConstraints(x / rows, (x % rows), 1, 1, 10, 10, GridBagConstraints.EAST, GridBagConstraints.BOTH, nullInsets, 0, 0)); } final Dimension screenResolution = Toolkit.getDefaultToolkit().getScreenSize(); final int availHeight = screenResolution.height - 80; final int availWidth = screenResolution.width - 30; final int availHeightRules = availHeight - 116; final int availWidthRules = availWidth - 16; final JScrollPane scroll = new JScrollPane(panel); scroll.setBorder(BorderFactory.createEmptyBorder()); scroll .setPreferredSize( new Dimension( (scroll.getPreferredSize().width > availWidthRules ? availWidthRules : scroll.getPreferredSize().width + (scroll.getPreferredSize().height > availHeightRules ? 25 : 0)), (scroll.getPreferredSize().height > availHeightRules ? availHeightRules : scroll.getPreferredSize().height + (scroll.getPreferredSize().width > availWidthRules ? 25 : 0)))); this.add(scroll, new GridBagConstraints(0, 1, 30, 1, 100, 100, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(8, 8, 8, 4), 0, 0)); // final int startY = m_rules.size() / rows; this.add(m_left, new GridBagConstraints(0, 2, 30, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(8, 8, 0, 12), 0, 0)); m_done = new JButton(m_done_action); this.add(m_done, new GridBagConstraints(0, 3, 30, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 8, 0), 0, 0)); this.setMaximumSize(new Dimension(availWidth, availHeight)); } // This method can be overridden by subclasses protected void setLeft(final ResourceCollection left, final int totalUnits) { m_left.setText(String.format( "%d total units purchased. You have %s left.", totalUnits, ResourceCollectionUtils.getProductionResources(left))); } Action m_done_action = SwingAction.of("Done", e -> m_dialog.setVisible(false)); private IntegerMap<ProductionRule> getProduction() { final IntegerMap<ProductionRule> prod = new IntegerMap<>(); for (final Rule rule : m_rules) { final int quantity = rule.getQuantity(); if (quantity != 0) { prod.put(rule.getProductionRule(), quantity); } } return prod; } // This method can be overridden by subclasses protected void calculateLimits() { // final IntegerMap<Resource> cost; final ResourceCollection resources = getResources(); final ResourceCollection spent = new ResourceCollection(m_data); int totalUnits = 0; for (final Rule current : m_rules) { spent.add(current.getCost(), current.getQuantity()); totalUnits += current.getQuantity() * current.getProductionRule().getResults().totalValues(); } final ResourceCollection leftToSpend = resources.difference(spent); setLeft(leftToSpend, totalUnits); for (final Rule current : m_rules) { int max = leftToSpend.fitsHowOften(current.getCost()); max += current.getQuantity(); current.setMax(max); } } protected 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? 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 { if (m_id == null || m_id.isNull()) { return new ResourceCollection(m_data); } return m_id.getResources(); } } class Rule { private final IntegerMap<Resource> m_cost; private int m_quantity; private final ProductionRule m_rule; private final PlayerID id; private final Set<ScrollableTextField> m_textFields = new HashSet<>(); protected JPanel getPanelComponent() { final JPanel panel = new JPanel(); final ScrollableTextField i_text = new ScrollableTextField(0, Integer.MAX_VALUE); i_text.setValue(m_quantity); panel.setLayout(new GridBagLayout()); final JLabel info = new JLabel(" "); final JLabel name = new JLabel(" "); final Color defaultForegroundLabelColor = name.getForeground(); Optional<ImageIcon> icon = Optional.empty(); final StringBuilder tooltip = new StringBuilder(); final Set<NamedAttachable> results = new HashSet<>(m_rule.getResults().keySet()); final Iterator<NamedAttachable> iter = results.iterator(); while (iter.hasNext()) { final NamedAttachable resourceOrUnit = iter.next(); if (resourceOrUnit instanceof UnitType) { final UnitType type = (UnitType) resourceOrUnit; icon = m_uiContext.getUnitImageFactory().getIcon(type, id, m_data, false, false); final UnitAttachment attach = UnitAttachment.get(type); final int attack = attach.getAttack(id); final int movement = attach.getMovement(id); final int defense = attach.getDefense(id); info.setText(attack + "/" + defense + "/" + movement); tooltip.append(type.getName()).append(": ").append(type.getTooltip(id)); name.setText(type.getName()); if (attach.getConsumesUnits() != null && attach.getConsumesUnits().totalValues() == 1) { name.setForeground(Color.CYAN); } else if (attach.getConsumesUnits() != null && attach.getConsumesUnits().totalValues() > 1) { name.setForeground(Color.BLUE); } else { name.setForeground(defaultForegroundLabelColor); } } else if (resourceOrUnit instanceof Resource) { final Resource resource = (Resource) resourceOrUnit; icon = Optional.of(m_uiContext.getResourceImageFactory().getIcon(resource, m_data, true)); info.setText("resource"); tooltip.append(resource.getName()).append(": resource"); name.setText(resource.getName()); name.setForeground(Color.GREEN); } if (iter.hasNext()) { tooltip.append("<br /><br /><br /><br />"); } } final int numberOfUnitsGiven = m_rule.getResults().totalValues(); String text; if (numberOfUnitsGiven > 1) { text = "<html> x " + ResourceCollection.toStringForHTML(m_cost, m_data) + "<br>" + "for " + numberOfUnitsGiven + "<br>" + " units</html>"; } else { 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 String toolTipText = "<html>" + tooltip.toString() + "</html>"; info.setToolTipText(toolTipText); label.setToolTipText(toolTipText); final int space = 8; // change name color for 'upgrades and consumes' unit types panel.add(name, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2, 0, 0, 0), 0, 0)); panel.add(label, new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, space, space, space), 0, 0)); panel.add(info, new GridBagConstraints(0, 2, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, space, space, space), 0, 0)); panel.add(i_text, new GridBagConstraints(0, 3, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(10, space, space, space), 0, 0)); i_text.addChangeListener(m_listener); m_textFields.add(i_text); panel.setBorder(new EtchedBorder()); return panel; } Rule(final ProductionRule rule, final PlayerID id) { m_rule = rule; m_cost = rule.getCosts(); this.id = id; } IntegerMap<Resource> getCost() { return m_cost; } int getQuantity() { return m_quantity; } void setQuantity(final int quantity) { m_quantity = quantity; for (final ScrollableTextField textField : m_textFields) { if (textField.getValue() != quantity) { textField.setValue(quantity); } } } ProductionRule getProductionRule() { return m_rule; } void setMax(final int max) { for (final ScrollableTextField textField : m_textFields) { textField.setMax(max); } } private final ScrollableTextFieldListener m_listener = new ScrollableTextFieldListener() { @Override public void changedValue(final ScrollableTextField stf) { if (stf.getValue() != m_quantity) { m_quantity = stf.getValue(); calculateLimits(); for (final ScrollableTextField textField : m_textFields) { if (!stf.equals(textField)) { textField.setValue(m_quantity); } } } } }; } }