package games.strategy.triplea.ui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import com.google.common.collect.Sets; import games.strategy.engine.data.GameData; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.triplea.delegate.AbstractUndoableMove; import games.strategy.triplea.util.UnitCategory; import games.strategy.triplea.util.UnitSeperator; public abstract class AbstractUndoableMovesPanel extends JPanel { private static final long serialVersionUID = 1910945925958952416L; protected List<AbstractUndoableMove> m_moves; protected final GameData m_data; protected final AbstractMovePanel m_movePanel; protected JScrollPane scroll; // TODO replace this Integer with a int primitive... Using null as toggle switch is bad code protected Integer scrollBarPreviousValue = null; protected Integer previousVisibleIndex = null; public AbstractUndoableMovesPanel(final GameData data, final AbstractMovePanel movePanel) { m_data = data; m_movePanel = movePanel; m_moves = Collections.emptyList(); } public void setMoves(final List<AbstractUndoableMove> m_undoableMoves) { m_moves = m_undoableMoves; SwingUtilities.invokeLater(() -> initLayout()); } public void undoMoves(final Map<Territory, List<Unit>> highlightUnitByTerritory) { final Set<Unit> units = Sets.newHashSet(); for (final List<Unit> highlightedUnits : highlightUnitByTerritory.values()) { units.addAll(highlightedUnits); } m_movePanel.undoMoves(units); } private void initLayout() { removeAll(); setLayout(new BorderLayout()); final JPanel items = new JPanel(); items.setLayout(new BoxLayout(items, BoxLayout.Y_AXIS)); // we want the newest move at the top m_moves = new ArrayList<>(m_moves); Collections.reverse(m_moves); final Iterator<AbstractUndoableMove> iter = m_moves.iterator(); if (iter.hasNext()) { add(new JLabel((this instanceof UndoablePlacementsPanel) ? "Placements:" : "Moves:"), BorderLayout.NORTH); } int scrollIncrement = 10; final Dimension seperatorSize = new Dimension(150, 20); while (iter.hasNext()) { final AbstractUndoableMove item = iter.next(); final JComponent moveComponent = createComponentForMove(item); scrollIncrement = moveComponent.getPreferredSize().height; items.add(moveComponent); if (iter.hasNext()) { final JSeparator seperator = new JSeparator(SwingConstants.HORIZONTAL); seperator.setPreferredSize(seperatorSize); seperator.setMaximumSize(seperatorSize); items.add(seperator); } } if (m_movePanel.getUndoableMoves() != null && m_movePanel.getUndoableMoves().size() > 1) { final JButton undoAllButton = new JButton("Undo All"); undoAllButton.addActionListener(new UndoAllMovesActionListener()); items.add(undoAllButton); } final int scrollIncrementFinal = scrollIncrement + seperatorSize.height; // JScrollPane scroll = new JScrollPane(items); scroll = new JScrollPane(items) { private static final long serialVersionUID = -1064967105431785533L; @Override public void paint(final Graphics g) { if (previousVisibleIndex != null) { items.scrollRectToVisible(new Rectangle(0, scrollIncrementFinal * ((m_moves.size()) - previousVisibleIndex), 1, scrollIncrementFinal)); previousVisibleIndex = null; } super.paint(g); } }; scroll.setBorder(null); scroll.getVerticalScrollBar().setUnitIncrement(scrollIncrementFinal); if (scrollBarPreviousValue != null) { scroll.getVerticalScrollBar().setValue(scrollBarPreviousValue); scrollBarPreviousValue = null; } add(scroll, BorderLayout.CENTER); SwingUtilities.invokeLater(() -> validate()); } private JComponent createComponentForMove(final AbstractUndoableMove move) { final Box unitsBox = new Box(BoxLayout.X_AXIS); unitsBox.add(new JLabel((move.getIndex() + 1) + ") ")); final Collection<UnitCategory> unitCategories = UnitSeperator.categorize(move.getUnits()); final Iterator<UnitCategory> iter = unitCategories.iterator(); final Dimension buttonSize = new Dimension(80, 22); while (iter.hasNext()) { final UnitCategory category = iter.next(); final Optional<ImageIcon> icon = m_movePanel.getMap().getUIContext().getUnitImageFactory().getIcon(category.getType(), category.getOwner(), m_data, category.hasDamageOrBombingUnitDamage(), category.getDisabled()); if (icon.isPresent()) { final JLabel label = new JLabel("x" + category.getUnits().size() + " ", icon.get(), SwingConstants.LEFT); unitsBox.add(label); } } unitsBox.add(Box.createHorizontalGlue()); final JLabel text = new JLabel(move.getMoveLabel()); final Box textBox = new Box(BoxLayout.X_AXIS); textBox.add(text); textBox.add(Box.createHorizontalGlue()); final JButton cancelButton = new JButton(new UndoMoveActionListener(move.getIndex())); setSize(buttonSize, cancelButton); final JButton viewbutton = new JButton(new ViewAction(move)); setSize(buttonSize, viewbutton); final Box buttonsBox = new Box(BoxLayout.X_AXIS); buttonsBox.add(viewbutton); buttonsBox.add(cancelButton); buttonsBox.add(Box.createHorizontalGlue()); final Box rVal = new Box(BoxLayout.Y_AXIS); rVal.add(unitsBox); rVal.add(textBox); rVal.add(buttonsBox); rVal.add(new JLabel(" ")); return rVal; } public int getCountOfMovesMade() { return m_moves.size(); } protected void setSize(final Dimension buttonSize, final JButton cancelButton) { cancelButton.setMinimumSize(buttonSize); cancelButton.setPreferredSize(buttonSize); cancelButton.setMaximumSize(buttonSize); } class UndoMoveActionListener extends AbstractAction { private static final long serialVersionUID = -397312652244693138L; private final int m_moveIndex; public UndoMoveActionListener(final int index) { super("Undo"); m_moveIndex = index; } @Override public void actionPerformed(final ActionEvent e) { // Record position of scroll bar as percentage. scrollBarPreviousValue = scroll.getVerticalScrollBar().getValue(); final String error = m_movePanel.undoMove(m_moveIndex); if (error == null) { previousVisibleIndex = Math.max(0, m_moveIndex - 1); } else { previousVisibleIndex = null; } } } class UndoAllMovesActionListener extends AbstractAction { private static final long serialVersionUID = 7908136093303143896L; public UndoAllMovesActionListener() { super("UndoAllMoves"); } @Override public void actionPerformed(final ActionEvent e) { final int moveCount = m_movePanel.getUndoableMoves().size(); final boolean suppressErrorMsgToUser = true; for (int i = moveCount - 1; i >= 0; i--) { m_movePanel.undoMove(i, suppressErrorMsgToUser); } } } class ViewAction extends AbstractAction { private static final long serialVersionUID = -6999284663802575467L; private final AbstractUndoableMove m_move; public ViewAction(final AbstractUndoableMove move) { super("Show"); m_move = move; } @Override public void actionPerformed(final ActionEvent e) { m_movePanel.cancelMove(); if (!m_movePanel.getMap().isShowing(m_move.getEnd())) { m_movePanel.getMap().centerOn(m_move.getEnd()); } specificViewAction(m_move); } } protected abstract void specificViewAction(final AbstractUndoableMove move); }