package games.strategy.triplea.ui;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import games.strategy.util.ThreadUtil;
/**
* A panel for showing the battle steps in a display.
* Contains code for walking from the current step, to a given step
* there is a delay while we walk so that the user can see the steps progression.
* Users of this class should deactive it after they are done.
*/
class BattleStepsPanel extends JPanel implements Active {
private static final long serialVersionUID = 911638924664810435L;
private static final Logger log = Logger.getLogger(BattleStepsPanel.class.getName());
// if this is the target step, we want to walk to the last step
private static final String LAST_STEP = "NULL MARKER FOR LAST STEP";
private final DefaultListModel<String> m_listModel = new DefaultListModel<>();
private final JList<String> m_list = new JList<>(m_listModel);
private final MyListSelectionModel m_listSelectionModel = new MyListSelectionModel();
// the step we want to reach
private String m_targetStep = null;
// all changes to state should be done while locked on this object.
// when we reach the target step, or when we want to walk the step
// notifyAll on this object
private final Object m_mutex = new Object();
private final List<CountDownLatch> m_waiters = new ArrayList<>();
private boolean m_hasWalkThread = false;
BattleStepsPanel() {
setLayout(new BorderLayout());
add(m_list, BorderLayout.CENTER);
m_list.setBackground(this.getBackground());
m_list.setSelectionModel(m_listSelectionModel);
}
@Override
public void deactivate() {
wakeAll();
}
private void wakeAll() {
synchronized (m_mutex) {
for (final CountDownLatch l : m_waiters) {
l.countDown();
}
m_waiters.clear();
}
}
/**
* Set the steps given, setting the selected step to the first step.
*/
public void listBattle(final List<String> steps) {
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Not in dispatch thread");
}
synchronized (m_mutex) {
m_listModel.removeAllElements();
final Iterator<String> iter = steps.iterator();
while (iter.hasNext()) {
m_listModel.addElement(iter.next());
}
m_listSelectionModel.hiddenSetSelectionInterval(0);
if (!steps.contains(m_targetStep)) {
m_targetStep = null;
}
}
validate();
}
private void clearTargetStep() {
synchronized (m_mutex) {
m_targetStep = null;
}
wakeAll();
}
private boolean doneWalkingSteps() {
synchronized (m_mutex) {
// not looking for anything
if (m_targetStep == null) {
return true;
}
// we cant find it, something is wrong
if (!m_targetStep.equals(LAST_STEP) && m_listModel.lastIndexOf(m_targetStep) == -1) {
new IllegalStateException("Step not found:" + m_targetStep + " in:" + m_listModel).printStackTrace();
clearTargetStep();
return true;
}
// at end, we are done
if (m_targetStep.equals(LAST_STEP) && m_list.getSelectedIndex() == m_listModel.getSize() - 1) {
return true;
}
// we found it, we are done
if (m_targetStep.equals(m_list.getSelectedValue())) {
return true;
}
}
return false;
}
/**
* Walks through and pause at each list item until we find our target.
*/
private void walkStep() {
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Wrong thread");
}
if (doneWalkingSteps()) {
wakeAll();
return;
}
int index = m_list.getSelectedIndex() + 1;
if (index >= m_list.getModel().getSize()) {
index = 0;
}
m_listSelectionModel.hiddenSetSelectionInterval(index);
waitThenWalk();
}
private void waitThenWalk() {
new Thread(() -> {
synchronized (m_mutex) {
if (m_hasWalkThread) {
return;
}
m_hasWalkThread = true;
}
try {
if (ThreadUtil.sleep(330)) {
SwingUtilities.invokeLater(this::walkStep);
}
} finally {
synchronized (m_mutex) {
m_hasWalkThread = false;
}
}
}).start();
}
/**
* This method blocks until the last step is reached, unless
* this method is called from the swing event thread.
*/
public void walkToLastStep() {
synchronized (m_mutex) {
m_targetStep = LAST_STEP;
}
goToTarget();
}
/**
* Set the target step for this panel
* This method returns immediatly, and must be called from the swing event thread.
*/
public void setStep(final String step) {
synchronized (m_mutex) {
if (m_listModel.indexOf(step) != -1) {
m_targetStep = step;
} else {
log.info("Could not find step name:" + step);
}
}
goToTarget();
}
private void goToTarget() {
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Not swing event thread");
}
waitThenWalk();
}
}
/**
* Doesnt allow the user to change the selection, must be done through
* hiddenSetSelectionInterval.
*/
class MyListSelectionModel extends DefaultListSelectionModel {
private static final long serialVersionUID = -4359950441657840015L;
@Override
public void setSelectionInterval(final int index0, final int index1) {}
public void hiddenSetSelectionInterval(final int index) {
super.setSelectionInterval(index, index);
}
}