package games.strategy.triplea.ui; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import com.google.common.collect.Sets; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Unit; import games.strategy.engine.gamePlayer.IPlayerBridge; import games.strategy.triplea.delegate.UndoableMove; import games.strategy.triplea.delegate.dataObjects.MoveDescription; import games.strategy.triplea.delegate.remote.IAbstractMoveDelegate; public abstract class AbstractMovePanel extends ActionPanel { private static final long serialVersionUID = -4153574987414031433L; private static final String s_MOVE_PANEL_CANCEL = "movePanel.cancel"; private static final Logger s_logger = Logger.getLogger(MovePanel.class.getName()); private static final int s_entryPadding = 15; private final TripleAFrame m_frame; private boolean m_listening = false; private final JLabel m_actionLabel = new JLabel(); protected MoveDescription m_moveMessage; protected List<UndoableMove> m_undoableMoves; protected AbstractAction m_doneMove = new AbstractAction("Done") { private static final long serialVersionUID = -6497408896615920650L; @Override public void actionPerformed(final ActionEvent e) { if (doneMoveAction()) { m_moveMessage = null; release(); } } }; private final Action m_DONE_MOVE_ACTION = new WeakAction("Done", m_doneMove); private final Action m_cancelMove = new AbstractAction("Cancel") { private static final long serialVersionUID = -257745862234175428L; @Override public void actionPerformed(final ActionEvent e) { cancelMoveAction(); if (m_frame != null) { m_frame.clearStatusMessage(); } this.setEnabled(false); m_CANCEL_MOVE_ACTION.setEnabled(false); } }; public AbstractMovePanel(final GameData data, final MapPanel map, final TripleAFrame frame) { super(data, map); m_frame = frame; m_CANCEL_MOVE_ACTION.setEnabled(false); m_undoableMoves = Collections.emptyList(); } /* * sub-classes method for done handling */ protected abstract boolean doneMoveAction(); /* * sub-classes method for cancel handling */ protected abstract void cancelMoveAction(); private final AbstractAction m_CANCEL_MOVE_ACTION = new WeakAction("Cancel", m_cancelMove); protected AbstractUndoableMovesPanel m_undoableMovesPanel; private IPlayerBridge m_bridge; protected IPlayerBridge getPlayerBridge() { return m_bridge; } // m_frame methods protected final void clearStatusMessage() { m_frame.clearStatusMessage(); } protected final void setStatusErrorMessage(final String message) { m_frame.setStatusErrorMessage(message); } protected final void setStatusWarningMessage(final String message) { m_frame.setStatusWarningMessage(message); } protected final boolean getListening() { return m_listening; } protected final void setMoveMessage(final MoveDescription message) { m_moveMessage = message; } protected final List<UndoableMove> getUndoableMoves() { return m_undoableMoves; } protected final void enableCancelButton() { m_CANCEL_MOVE_ACTION.setEnabled(true); } /** * @return m_bridge.getGameData() */ protected final GameData getGameData() { return m_bridge.getGameData(); } private IAbstractMoveDelegate getMoveDelegate() { return (IAbstractMoveDelegate) m_bridge.getRemoteDelegate(); } @SuppressWarnings("unchecked") protected final void updateMoves() { m_undoableMoves = (List<UndoableMove>) getMoveDelegate().getMovesMade(); m_undoableMovesPanel.setMoves(new ArrayList<>(m_undoableMoves)); } public final void cancelMove() { m_CANCEL_MOVE_ACTION.actionPerformed(null); } public final String undoMove(final int moveIndex) { return undoMove(moveIndex, false); } /** * Executes an undo move for any of the units passed in as a parameter. * * <p> * "Cannot undo" Error messages are suppressed if any moves cannot be undone * (at least until we come up with a way to deal with "n" reasons for an undo * failure rather than just one) * </p> */ public void undoMoves(final Set<Unit> units) { @SuppressWarnings("unchecked") final Set<UndoableMove> movesToUndo = getMovesToUndo(units, (List<Object>) getMoveDelegate().getMovesMade()); if (movesToUndo.size() == 0) { final String error = "Could not undo any moves, check that the unit has moved and that you can undo the move normally"; JOptionPane.showMessageDialog(getTopLevelAncestor(), error, "Could not undo move", JOptionPane.ERROR_MESSAGE); return; } undoMovesInReverseOrder(movesToUndo); } private static Set<UndoableMove> getMovesToUndo(final Set<Unit> units, final List<Object> movesMade) { final Set<UndoableMove> movesToUndo = Sets.newHashSet(); if (movesMade != null) { for (final Object undoableMoveObject : movesMade) { if (undoableMoveObject != null) { final UndoableMove move = (UndoableMove) undoableMoveObject; if (move.containsAnyOf(units) && move.getcanUndo()) { movesToUndo.add(move); } } } } return movesToUndo; } /* * Undo moves in reverse order, from largest index to smallest. Undo will reorder move index numbers, so going top * down avoids this renumbering. */ private void undoMovesInReverseOrder(final Set<UndoableMove> movesToUndo) { final List<Integer> moveIndexes = getSortedMoveIndexes(movesToUndo); for (int i = moveIndexes.size() - 1; i >= 0; i--) { undoMove(moveIndexes.get(i)); } } private static List<Integer> getSortedMoveIndexes(final Set<UndoableMove> moves) { final List<Integer> moveIndexes = new ArrayList<>(); for (final UndoableMove move : moves) { moveIndexes.add(move.getIndex()); } Collections.sort(moveIndexes); return moveIndexes; } protected final String undoMove(final int moveIndex, final boolean suppressError) { // clean up any state we may have m_CANCEL_MOVE_ACTION.actionPerformed(null); // undo the move final String error = getMoveDelegate().undoMove(moveIndex); if (error != null && !suppressError) { JOptionPane.showMessageDialog(getTopLevelAncestor(), error, "Could not undo move", JOptionPane.ERROR_MESSAGE); } else { updateMoves(); } undoMoveSpecific(); return error; } /** * sub-classes method for undo handling. */ protected abstract void undoMoveSpecific(); protected final void cleanUp() { SwingUtilities.invokeLater(() -> { s_logger.fine("cleanup"); if (!m_listening) { throw new IllegalStateException("Not listening"); } m_listening = false; cleanUpSpecific(); m_bridge = null; m_CANCEL_MOVE_ACTION.setEnabled(false); final JComponent rootPane = getRootPane(); if (rootPane != null) { rootPane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), null); } removeAll(); REFRESH.run(); }); } /* * sub-classes method for clean-up */ protected abstract void cleanUpSpecific(); @Override public final void setActive(final boolean active) { super.setActive(active); SwingUtilities.invokeLater(() -> m_CANCEL_MOVE_ACTION.actionPerformed(null)); } protected final void display(final PlayerID id, final String actionLabel) { super.display(id); SwingUtilities.invokeLater(() -> { removeAll(); m_actionLabel.setText(id.getName() + actionLabel); add(leftBox(m_actionLabel)); if (setCancelButton()) { add(leftBox(new JButton(m_CANCEL_MOVE_ACTION))); } add(leftBox(new JButton(m_DONE_MOVE_ACTION))); addAdditionalButtons(); add(Box.createVerticalStrut(s_entryPadding)); add(m_undoableMovesPanel); add(Box.createGlue()); SwingUtilities.invokeLater(REFRESH); }); } protected void addAdditionalButtons() {} protected abstract boolean setCancelButton(); protected static JComponent leftBox(final JComponent c) { final Box b = new Box(BoxLayout.X_AXIS); b.add(c); b.add(Box.createHorizontalGlue()); return b; } protected final void setUp(final IPlayerBridge bridge) { SwingUtilities.invokeLater(() -> { s_logger.fine("setup"); setUpSpecific(); m_bridge = bridge; updateMoves(); if (m_listening) { throw new IllegalStateException("Not listening"); } m_listening = true; if (getRootPane() != null) { final String key = s_MOVE_PANEL_CANCEL; getRootPane().getActionMap().put(key, m_CANCEL_MOVE_ACTION); getRootPane().getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), key); } }); } /* * sub-classes method for set-up */ protected abstract void setUpSpecific(); protected void clearDependencies() { // used by some subclasses } public final MoveDescription waitForMove(final IPlayerBridge bridge) { setUp(bridge); waitForRelease(); cleanUp(); final MoveDescription rVal = m_moveMessage; m_moveMessage = null; clearDependencies(); return rVal; } }