/* * Created on 03.01.2004 */ package net.sf.colossus.gui; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.ScrollPane; import java.awt.SystemColor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import net.sf.colossus.client.GameClientSide; import net.sf.colossus.client.LegionClientSide; import net.sf.colossus.client.PlayerClientSide; import net.sf.colossus.client.PredictSplitNode; import net.sf.colossus.client.PredictSplits; import net.sf.colossus.common.Options; import net.sf.colossus.game.Creature; import net.sf.colossus.guiutil.KDialog; import net.sf.colossus.util.TimeFormats; import net.sf.colossus.variant.CreatureType; /** * A dialog that displays information about a single player and his legions. */ @SuppressWarnings("serial") public final class PlayerDetailsDialog extends KDialog { private final PlayerClientSide player; private final ClientGUI gui; private final JPanel mainPane; private final JButton refreshButton; private static final int MAX_CREATURE_COLUMNS = 6; private final static Insets INSETS = new Insets(5, 5, 5, 5); private final static GridBagConstraints LABEL_CONSTRAINT = new GridBagConstraints(); static { LABEL_CONSTRAINT.gridx = GridBagConstraints.RELATIVE; LABEL_CONSTRAINT.gridy = GridBagConstraints.RELATIVE; LABEL_CONSTRAINT.anchor = GridBagConstraints.WEST; LABEL_CONSTRAINT.insets = INSETS; } private final static GridBagConstraints VALUE_CONSTRAINT = new GridBagConstraints(); static { VALUE_CONSTRAINT.gridx = GridBagConstraints.RELATIVE; VALUE_CONSTRAINT.gridy = GridBagConstraints.RELATIVE; VALUE_CONSTRAINT.anchor = GridBagConstraints.WEST; VALUE_CONSTRAINT.insets = INSETS; } private final static GridBagConstraints FIRST_LABEL_CONSTRAINT = new GridBagConstraints(); static { FIRST_LABEL_CONSTRAINT.gridx = 0; FIRST_LABEL_CONSTRAINT.gridy = GridBagConstraints.RELATIVE; FIRST_LABEL_CONSTRAINT.anchor = GridBagConstraints.WEST; FIRST_LABEL_CONSTRAINT.insets = INSETS; } private final static GridBagConstraints SECTION_TITLE_CONSTRAINT = new GridBagConstraints(); static { SECTION_TITLE_CONSTRAINT.gridx = GridBagConstraints.RELATIVE; SECTION_TITLE_CONSTRAINT.gridwidth = GridBagConstraints.REMAINDER; SECTION_TITLE_CONSTRAINT.anchor = GridBagConstraints.WEST; SECTION_TITLE_CONSTRAINT.weightx = 1; SECTION_TITLE_CONSTRAINT.insets = INSETS; } private final static GridBagConstraints HORIZONTAL_FILL_CONSTRAINT = new GridBagConstraints(); static { HORIZONTAL_FILL_CONSTRAINT.gridx = GridBagConstraints.RELATIVE; HORIZONTAL_FILL_CONSTRAINT.gridwidth = GridBagConstraints.REMAINDER; HORIZONTAL_FILL_CONSTRAINT.weightx = 1; } public PlayerDetailsDialog(final JFrame parentFrame, PlayerClientSide player, ClientGUI clientGui) { // TODO currently modal since we don't listen for updates yet --> fix super(parentFrame, "Details for player " + player.getName(), false); this.player = player; this.gui = clientGui; useSaveWindow(gui.getOptions(), "PlayerDetails", null); // int randomOffset = (int)(System.currentTimeMillis() % 5 - 10) * 10; // centerOnScreen(randomOffset, randomOffset); mainPane = new JPanel(); JScrollPane scrollPane = new JScrollPane(mainPane); scrollPane.setAlignmentX(ScrollPane.LEFT_ALIGNMENT); getContentPane().add(scrollPane); mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); mainPane.setAlignmentX(LEFT_ALIGNMENT); refreshButton = new JButton("Refresh"); refreshButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { refreshContent(); } }); addContent(mainPane); mainPane.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { dispose(); } }); setDefaultCloseOperation(DISPOSE_ON_CLOSE); SwingUtilities.invokeLater(new Runnable() { public void run() { pack(); setVisible(true); } }); } private void refreshContent() { mainPane.removeAll(); addContent(mainPane); pack(); } private void addContent(JPanel mainPane) { mainPane.add(createHeader()); mainPane.add(createSummaryTable()); mainPane.add(createLegionsTable()); mainPane.add(createCreaturesTable()); } private String getInfoText() { GameClientSide g = gui.getGameClientSide(); return "turn " + g.getTurnNumber() + ", " + g.getActivePlayer() + " " + g.getPhase().getDoesWhat(); } private Box createHeader() { Box labelBox = new Box(BoxLayout.X_AXIS); labelBox.setAlignmentX(LEFT_ALIGNMENT); labelBox.setBorder(new EmptyBorder(5, 5, 5, 5)); JLabel refreshedLabel = new JLabel("Generated: " + TimeFormats.getCurrentTimeLocalized() + " (" + getInfoText() + ") ", JLabel.LEFT); refreshedLabel.setHorizontalAlignment(JLabel.LEFT); refreshedLabel.setAlignmentX(LEFT_ALIGNMENT); labelBox.add(refreshedLabel); labelBox.add(Box.createHorizontalGlue()); labelBox.add(refreshButton); return labelBox; } private JPanel createSummaryTable() { JPanel result = new JPanel(new GridBagLayout()); result.setAlignmentX(LEFT_ALIGNMENT); result.add(new JLabel("Player:"), FIRST_LABEL_CONSTRAINT); result.add(new JLabel(player.getName()), VALUE_CONSTRAINT); result.add(new JLabel("Type:"), LABEL_CONSTRAINT); String type = player.getType(); type = type.substring(type.lastIndexOf('.') + 1); result.add(new JLabel(type), VALUE_CONSTRAINT); result.add(new JPanel(), HORIZONTAL_FILL_CONSTRAINT); result.add(new JLabel("Score:"), FIRST_LABEL_CONSTRAINT); result.add(new JLabel(String.valueOf(player.getScore())), VALUE_CONSTRAINT); result.add(new JLabel("Titan power:"), LABEL_CONSTRAINT); result.add(new JLabel(String.valueOf(player.getTitanPower())), VALUE_CONSTRAINT); result.add(new JPanel(), HORIZONTAL_FILL_CONSTRAINT); return result; } private JPanel createLegionsTable() { JPanel result = new JPanel(new GridBagLayout()); result.setAlignmentX(LEFT_ALIGNMENT); JLabel sectionLabel = new JLabel( String.valueOf(player.getNumLegions()) + " legions:"); result.add(sectionLabel, SECTION_TITLE_CONSTRAINT); result.add(new JLabel("Legion"), FIRST_LABEL_CONSTRAINT); result.add(new JLabel(), LABEL_CONSTRAINT); result.add(new JLabel("Hex"), LABEL_CONSTRAINT); result.add(new JLabel("Content"), LABEL_CONSTRAINT); result.add(new JPanel(), HORIZONTAL_FILL_CONSTRAINT); boolean dubiousAsBlanks = gui.getOptions().getOption( Options.dubiousAsBlanks); List<LegionClientSide> legions = new ArrayList<LegionClientSide>(); if (gui.getOptions().getOption(Options.legionListByMarkerId)) { // TODO: this could probably been done much more elegant // but for now I just want it to work so that I can use it... List<String> markers = new ArrayList<String>(); for (LegionClientSide legion : player.getLegions()) { markers.add(legion.getMarkerId()); } Collections.sort(markers); for (String markerId : markers) { legions.add(player.getLegionByMarkerId(markerId)); } } else { legions.addAll(player.getLegions()); } for (LegionClientSide legion : legions) { result.add(new JLabel(legion.getMarkerId()), FIRST_LABEL_CONSTRAINT); result.add( new Marker(legion, 2 * Scale.get(), legion.getLongMarkerId()), LABEL_CONSTRAINT); result.add(new JLabel(legion.getCurrentHex().getLabel()), LABEL_CONSTRAINT); result.add(new LegionInfoPanel(legion, 2 * Scale.get(), 0, 0, true, gui.getEffectiveViewMode(), gui.getClient().isMyLegion(legion), dubiousAsBlanks, true, false), LABEL_CONSTRAINT); result.add(new JPanel(), HORIZONTAL_FILL_CONSTRAINT); } result.add(new JLabel("Split history:"), SECTION_TITLE_CONSTRAINT); if (gui.getGame().getTurnNumber() < 512) { JScrollPane splitNodesPanel = new JScrollPane( createSplitNodesPanel()); result.add(splitNodesPanel, SECTION_TITLE_CONSTRAINT); } else { result.add(new JLabel( " [ Too many turns! Table cannot be created. ] "), SECTION_TITLE_CONSTRAINT); } return result; } private JPanel createSplitNodesPanel() { JPanel result = new JPanel(new GridBagLayout()); result.setAlignmentX(LEFT_ALIGNMENT); PredictSplits ps = player.getPredictSplits(); PredictSplitNode root = ps.getRoot(); Map<PredictSplitNode, GridBagConstraints> layouts = new HashMap<PredictSplitNode, GridBagConstraints>(); GridBagConstraints rootConstraints = calculateSplitNodeLayout(root, 0, layouts); int totalHeight = rootConstraints.gridheight; for (Map.Entry<PredictSplitNode, GridBagConstraints> entry : layouts .entrySet()) { PredictSplitNode node = entry.getKey(); GridBagConstraints constraints = entry.getValue(); // Currently we just place a simple label with the name, colored in a way // that the length on the timeline gets clear. // TODO: replace with some graphical representation of the node JLabel label = new JLabel(node.getFullName()); label.setOpaque(true); label.setBackground(getIntermediateColor(SystemColor.control, Color.WHITE, constraints.gridy / (totalHeight - 1D))); result.add(label, constraints); } return result; } private Color getIntermediateColor(Color from, Color to, double pos) { assert (pos >= 0) && (pos <= 1) : "Position out of range [0,1]"; int red = (int)(from.getRed() * (1 - pos) + to.getRed() * pos); int green = (int)(from.getGreen() * (1 - pos) + to.getGreen() * pos); int blue = (int)(from.getBlue() * (1 - pos) + to.getBlue() * pos); int alpha = (int)(from.getAlpha() * (1 - pos) + to.getAlpha() * pos); return new Color(red, green, blue, alpha); } // TODO it might be possible to just place the nodes directly instead of using // the map to store the constraints first private GridBagConstraints calculateSplitNodeLayout(PredictSplitNode node, int y, Map<PredictSplitNode, GridBagConstraints> layouts) { // Layout concept: each parent spans the height of the two children, the // child that keeps the marker stays at the same level, the splitoff gets // placed just below the other. // Horizontally the legions are placed according to a timeline concept: from // the turn they were created to the turn they were split again. // TODO: what about killed legions? GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = node.getTurnCreated(); constraints.gridy = y; constraints.fill = GridBagConstraints.BOTH; if (node.getChild1() != null) { GridBagConstraints child1Layout = calculateSplitNodeLayout( node.getChild1(), y, layouts); GridBagConstraints child2Layout = calculateSplitNodeLayout( node.getChild2(), y + child1Layout.gridheight, layouts); constraints.gridheight = child1Layout.gridheight + child2Layout.gridheight; constraints.gridwidth = node.getChild1().getTurnCreated() - node.getTurnCreated(); } else { constraints.gridheight = 1; constraints.gridwidth = gui.getGame().getTurnNumber() - node.getTurnCreated(); } layouts.put(node, constraints); return constraints; } private JPanel createCreaturesTable() { JPanel result = new JPanel(new GridBagLayout()); result.setAlignmentX(LEFT_ALIGNMENT); result.add(new JLabel(String.valueOf(player.getNumCreatures()) + " creatures (" + player.getTotalPointValue() + " points):"), SECTION_TITLE_CONSTRAINT); Map<CreatureType, Integer> creatureMap = new TreeMap<CreatureType, Integer>( CreatureType.NAME_ORDER); for (LegionClientSide legion : player.getLegions()) { for (Creature creature : legion.getCreatures()) { Integer count = creatureMap.get(creature.getType()); if (count == null) { count = Integer.valueOf(1); } else { count = Integer.valueOf(count.intValue() + 1); } creatureMap.put(creature.getType(), count); } } int i = 0; int total = creatureMap.size(); // one row plus another every time we get passed N times the maximum // per row -- this is effectively a rounding up on ints int rows = (total - 1) / MAX_CREATURE_COLUMNS + 1; // same rounding up trick int cols = (total - 1) / rows + 1; for (Map.Entry<CreatureType, Integer> entry : creatureMap.entrySet()) { CreatureType type = entry.getKey(); Integer count = entry.getValue(); GridBagConstraints chitConstraint; if (i == 0) { chitConstraint = FIRST_LABEL_CONSTRAINT; } else { chitConstraint = LABEL_CONSTRAINT; } result.add(Chit.newCreatureChit(2 * Scale.get(), type), chitConstraint); result.add(new JLabel(count.toString()), VALUE_CONSTRAINT); i = (i + 1) % cols; if (i == 0) { result.add(new JPanel(), HORIZONTAL_FILL_CONSTRAINT); } } return result; } }