//
// @(#)F2FOrderDisplayPanel.java 6/2003
//
// Copyright 2003 Zachary DelProposto. All rights reserved.
// Use is subject to license terms.
//
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// Or from http://www.gnu.org/
//
package dip.gui;
import dip.order.*;
import dip.world.*;
import dip.gui.undo.*;
import dip.gui.order.GUIOrder;
import dip.gui.map.SVGColorParser;
import dip.gui.swing.ColorRectIcon;
import dip.misc.Utils;
import dip.process.Adjustment;
import dip.misc.Log;
import dip.gui.map.MapMetadata;
import dip.order.result.Result;
import dip.order.result.OrderResult;
import cz.autel.dmi.*; // HIGLayout
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.undo.*;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.event.*;
import java.util.*;
import java.beans.*;
import java.text.MessageFormat;
/**
* The F2FOrderDisplayPanel: displayer of orders for Face-to-Face (F2F) games.
* <p>
* This is a subclass of ODP that manages F2F games.
*
*/
public class F2FOrderDisplayPanel extends OrderDisplayPanel
{
// i18n constants
private static final String SUBMIT_BUTTON_TEXT = "F2FODP.button.submit.text";
private static final String SUBMIT_BUTTON_TIP = "F2FODP.button.submit.tooltip";
private static final String ALLPOWERS_TAB_LABEL = "F2FODP.tab.label.allpowers";
private static final String CONFIRM_TITLE = "F2FODP.confirm.title";
private static final String CONFIRM_TEXT = "F2FODP.confirm.text";
private static final String ENTER_ORDERS_TEXT = "F2FODP.button.enterorders.text";
private static final String ENTER_ORDERS_TIP = "F2FODP.button.enterorders.tooltip";
// instance fields
private JTabbedPane tabPane = null;
private JPanel main = null;
private JButton submit = null;
private JButton enterOrders = null;
private F2FState tempState = null;
private F2FState entryState = null;
private MapMetadata mmd = null;
private TabListener tabListener = null;
private JPanel buttonPanel = null; // holds submit/enter orders button
// hold resolved and next TurnStates
private TurnState resolvedTS = null;
private TurnState nextTS = null;
private boolean isReviewingResolvedTS = false;
/**
* Creates an F2FOrderDisplayPanel
*/
public F2FOrderDisplayPanel(ClientFrame clientFrame)
{
super(clientFrame);
makeF2FLayout();
}// F2FOrderDisplayPanel()
/** Cleanup */
public void close()
{
super.close();
}// close()
/** Handle the Submit button events */
private class SubmissionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
// confirm submission
final int result = JOptionPane.showConfirmDialog(
clientFrame,
Utils.getLocalString(CONFIRM_TEXT),
Utils.getLocalString(CONFIRM_TITLE),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE );
if(result != JOptionPane.YES_OPTION)
{
return;
}
// we are confirmed
//
// a submission (really, just the first) disables the
// 'all' powers tab from being selected
tabPane.setEnabledAt(0, false);
// filter out undo actions, so they are not seen by other powers.
// limit so that a power cannot undo the turn resolution once
// the 'all' tab is locked
clientFrame.getUndoRedoManager().filterF2F();
// disable this power tab
final int idx = tabPane.getSelectedIndex();
if(idx == 0) { throw new IllegalStateException(); }
tabPane.setEnabledAt(idx, false);
// bring up a random enabled next power. If all powers
// have submitted orders, resolve.
// we do this by checking which tabs are (or are not) enabled.
// when all tabs have been disabled, resolution takes place. Since
// eliminated powers don't have tabs, this works nicely.
int nextAvailable = selectNextRandomTab(tabPane);
if(nextAvailable == -1)
{
entryState = null;
clientFrame.resolveOrders();
}
else
{
setPowersDisplayed(nextAvailable);
tabPane.setSelectedIndex(nextAvailable);
setSubmitEnabled();
saveEntryState();
}
}// actionPerformed()
}// inner class SubmissionListener
/** Handle the "Enter Orders" button event */
private class EnterOrdersListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
isReviewingResolvedTS = false;
resolvedTS = null;
final TurnState tmpTS = nextTS;
nextTS = null;
if(tmpTS != null)
{
clientFrame.fireTurnstateChanged(tmpTS);
}
changeButton(submit);
}
}// inner class EnterOrdersListener
/** Handle Tab Pane events */
private class TabListener implements ChangeListener
{
private boolean isEnabled = true;
public synchronized void setEnabled(boolean value)
{
isEnabled = value;
}// setEnabled()
public synchronized void forceUpdate()
{
if(isEnabled)
{
update();
}
}// forceUpdate()
public synchronized void stateChanged(ChangeEvent e)
{
if(isEnabled)
{
update();
}
}// stateChanged()
private void update()
{
// set the panel
final int idx = tabPane.getSelectedIndex();
if(idx != -1)
{
JPanel panel = (JPanel) tabPane.getComponentAt(idx);
panel.add(main, BorderLayout.CENTER);
}
// set what we can and cannot display
if(turnState != null)
{
setSubmitEnabled();
setPowersDisplayed(idx);
saveEntryState();
}
}// update()
}// inner class TabListener
/** Extended F2FPropertyListener */
protected class F2FPropertyListener extends ODPPropertyListener
{
public void actionTurnstateChanged(TurnState ts)
{
if(resolvedTS != null && !isReviewingResolvedTS)
{
isReviewingResolvedTS = true;
changeButton(enterOrders);
enterOrders.setEnabled( (nextTS != null) );
// we're in the fireTurnstateChanged() thread/event loop;
// fire this event outside, so that everyone can receive it.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
clientFrame.fireTurnstateChanged(resolvedTS);
}
});
}
else
{
// if we use "history | next" to go to the post-resolved state
// instead of clicking 'enter orders' button, reset the state as
// if we had pressed the button.
if(isReviewingResolvedTS && ts == nextTS)
{
enterOrders.doClick(); // this will call actionTurnstateChanged() again
return; // so that's why we can return
}
super.actionTurnstateChanged(ts);
createTabs();
if(tempState != null)
{
setupState(tempState);
tempState = null;
}
if(!turnState.isResolved() && entryState != null)
{
setupState(entryState);
}
setSubmitEnabled();
}
}// actionTurnstateChanged()
public void actionTurnstateResolved(TurnState ts)
{
super.actionTurnstateResolved(ts);
resolvedTS = ts;
}// actionTurnstateResolved()
public void actionTurnstateAdded(TurnState ts)
{
super.actionTurnstateAdded(ts);
nextTS = ts;
}// actionTurnstateAdded()
public void actionMMDReady(MapMetadata mmd)
{
super.actionMMDReady(mmd);
F2FOrderDisplayPanel.this.mmd = mmd;
setTabIcons();
}// actionMMDReady()
public void actionModeChanged(String mode)
{
super.actionModeChanged(mode);
if(mode == ClientFrame.MODE_ORDER)
{
// disable some menu options
// when in order mode.
ClientMenu cm = clientFrame.getClientMenu();
cm.setEnabled(ClientMenu.ORDERS_RESOLVE, false);
}
}// actionModeChanged()
}// nested class F2FPropertyListener
/** Sets the tab icons for each power. */
private void setTabIcons()
{
if(mmd != null)
{
final int tabCount = tabPane.getTabCount();
for(int i=1; i<tabCount; i++)
{
Power power = world.getMap().getPower( tabPane.getTitleAt(i) );
assert(power != null);
String colorName = mmd.getPowerColor(power);
Color color = SVGColorParser.parseColor(colorName);
tabPane.setIconAt( i, new ColorRectIcon(12,12, color) );
}
}
}// setTabIcons()
/**
* Determines when Submit button should be enabled or not.
* Disabled when looking at (reviewing) old turns, or
* if the 'all' tab is selected and looking at the current
* turn.
*/
private void setSubmitEnabled()
{
assert(turnState != null);
submit.setEnabled(false);
// if a tab is selected that is enabled and
// not the 'all' tab, submit should be enabled.
if(!turnState.isResolved())
{
int idx = tabPane.getSelectedIndex();
if( idx > 0 && tabPane.isEnabledAt(idx) )
{
submit.setEnabled(true);
}
}
}// setSubmitEnabled()
/** Change the Button in the ButtonPanel */
private void changeButton(JButton button)
{
buttonPanel.removeAll();
buttonPanel.add(button);
buttonPanel.validate();
}// changeButton()
/**
* Fires which powers are displayable for the given tab.
* Handles the All tab appropriately. (index 0).
*/
private void setPowersDisplayed(int tabIdx)
{
if(tabIdx == 0)
{
Power[] allPowers = world.getMap().getPowers();
clientFrame.fireDisplayablePowersChanged(clientFrame.getDisplayablePowers(), allPowers);
clientFrame.fireOrderablePowersChanged(clientFrame.getOrderablePowers(), new Power[0]);
}
else
{
// need to match by tab name, since if a power was eliminated
// the index will not correspond to Map.getPowers()
Power selectedPower = world.getMap().getPower( tabPane.getTitleAt(tabIdx) );
Power[] powerArray = new Power[] { selectedPower };
clientFrame.fireDisplayablePowersChanged(clientFrame.getDisplayablePowers(), powerArray);
clientFrame.fireOrderablePowersChanged(clientFrame.getOrderablePowers(), powerArray);
}
}// setPowersDisplayed()
/**
* Creates the Power tabs. Tabs are created for each
* Power that has not been eliminated or are inactive.
*/
private void createTabs()
{
assert(world != null);
assert(turnState != null);
// disable tab events
tabListener.setEnabled(false);
// enable 'all' tab if appropriate
// disable all orders from being entered, by default
tabPane.setEnabledAt(0, turnState.isResolved());
clientFrame.fireOrderablePowersChanged(clientFrame.getOrderablePowers(), new Power[0]);
// remove old tabs (except for 'all' tab)
// in reverse order
final int numTabs = tabPane.getTabCount();
for(int i=(numTabs-1); i>0; i--)
{
tabPane.removeTabAt(i);
}
// create new tabs
// disable tabs for powers that don't require orders during
// retreat or adjustment phases, if appropriate.
final Power[] allPowers = world.getMap().getPowers();
Adjustment.AdjustmentInfoMap f2fAdjMap = Adjustment.getAdjustmentInfo(turnState,
world.getRuleOptions(), allPowers);
final Position pos = turnState.getPosition();
for(int i=0; i<allPowers.length; i++)
{
if( !pos.isEliminated(allPowers[i]) && allPowers[i].isActive() )
{
tabPane.addTab(allPowers[i].getName(), new JPanel(new BorderLayout()));
int tabIdx = tabPane.indexOfTab(allPowers[i].getName());
Adjustment.AdjustmentInfo adjInfo = f2fAdjMap.get(allPowers[i]);
if(turnState.getPhase().getPhaseType() == Phase.PhaseType.ADJUSTMENT)
{
if(adjInfo.getAdjustmentAmount() == 0)
{
tabPane.setEnabledAt(tabIdx, false);
}
}
else if(turnState.getPhase().getPhaseType() == Phase.PhaseType.RETREAT)
{
if(adjInfo.getDislodgedUnitCount() == 0)
{
tabPane.setEnabledAt(tabIdx, false);
}
}
}
}
// add colors
setTabIcons();
// create new randomized list for tab selection
createTabSelectionOrderList();
// enable tab events
tabListener.setEnabled(true);
// if not resolved, first tab is first power after 'all',
// that is not disabled. Otherwise, we will select the 'all' tab.
if(turnState.isResolved())
{
// at this point, tabPane.getSelectedIndex() == 0; thus if we set the
// index to 0, no 'changeevent' will be fired. We must force an update.
//
tabPane.setSelectedIndex(0); // doesn't force an update...
tabListener.forceUpdate(); // but this will
}
else
{
// disable tabs of powers that have already submitted orders!
if(entryState != null)
{
restoreState(entryState);
}
else
{
// select a random power (not "all") tab
tabPane.setSelectedIndex(selectNextRandomTab(tabPane));
}
}
}// createTabs()
/**
* Create a list containing tab indexes.
* This List is used to shuffle, so we can select the power
* tabs in a random and efficient way.
*/
private List createTabSelectionOrderList() {
final int tabCount = tabPane.getTabCount();
List tabSelectionOrderList = new ArrayList(tabCount);
// skip 'All' tab, start at one
for(int i=1; i<tabCount; i++)
{
tabSelectionOrderList.add(new Integer(i));
}
Collections.shuffle(tabSelectionOrderList);
return tabSelectionOrderList;
}
/**
* Select the next tab in a random way.
*
* @param tabPane the JTabbedPane containing the tabs
* @return the index of the selected tab
*/
private int selectNextRandomTab(JTabbedPane tabPane) {
List tabSelectionOrderList = createTabSelectionOrderList();
int nextAvailable = -1;
int currentTab;
for(int i=0; i<tabSelectionOrderList.size(); i++)
{
currentTab = ((Integer)tabSelectionOrderList.get(i)).intValue();
if(tabPane.isEnabledAt(currentTab))
{
nextAvailable = currentTab;
break;
}
}
return nextAvailable;
}
/** Create an extended property listener. */
protected AbstractCFPListener createPropertyListener()
{
return new F2FPropertyListener();
}// createPropertyListener()
/** Make the F2F layout. */
private void makeF2FLayout()
{
// submit button
submit = new JButton(Utils.getLocalString(SUBMIT_BUTTON_TEXT));
submit.setToolTipText(Utils.getLocalString(SUBMIT_BUTTON_TIP));
submit.addActionListener(new SubmissionListener());
enterOrders = new JButton(Utils.getLocalString(ENTER_ORDERS_TEXT));
enterOrders.setToolTipText(Utils.getLocalString(ENTER_ORDERS_TIP));
enterOrders.addActionListener(new EnterOrdersListener());
// center the buttonPanel button
buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.add(submit);
// we want to share the main panel between all tabs
// main panel layout
main = new JPanel();
int w1[] = { 0 };
int h1[] = { 0, 5, 0, 10, 0};
HIGLayout hl = new HIGLayout(w1, h1);
hl.setColumnWeight(1, 1);
hl.setRowWeight(1, 1);
main.setLayout(hl);
HIGConstraints c = new HIGConstraints();
main.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
main.add(orderListScrollPane, c.rc(1,1,"lrtb"));
main.add(makeSortPanel(), c.rc(3,1));
main.add(buttonPanel, c.rc(5,1));
tabPane = new JTabbedPane();
tabListener = new TabListener();
tabPane.addChangeListener(tabListener);
tabPane.setTabPlacement(JTabbedPane.TOP);
// we always have the 'all' tab (though it may be disabled at times)
// it should always be position 0
tabPane.addTab(Utils.getLocalString(ALLPOWERS_TAB_LABEL), new JPanel(new BorderLayout()));
// set the layout of F2FODP
setLayout(new BorderLayout());
add(tabPane, BorderLayout.CENTER);
}// makeF2FLayout()
/** Do nothing. We have our own layout method. */
protected void makeLayout()
{
// do nothing.
}// makeLayout()
/** Actually setup the state. */
private void setupState(F2FState state)
{
assert(turnState != null);
// set tab enablement
boolean[] enableds = state.getTabState();
for(int i=0; i<enableds.length; i++)
{
tabPane.setEnabledAt(i, enableds[i]);
}
// set selected tab
if(state.getCurrentPower() == null)
{
// if no tab selected, select 'all' (if resolved); otherwise,
// select a random tab.
if(turnState.isResolved())
{
tabPane.setSelectedIndex(0);
}
else
{
tabPane.setSelectedIndex(selectNextRandomTab(tabPane));
}
}
else
{
tabPane.setSelectedIndex( tabPane.indexOfTab(state.getCurrentPower().getName()) );
}
}// setupState()
/** Saves the current state, if appropriate. */
private void saveEntryState()
{
assert(turnState != null);
if(!turnState.isResolved())
{
// get selected tab
int idx = tabPane.getSelectedIndex();
Power selectedPower = world.getMap().getPower( tabPane.getTitleAt(idx) );
boolean[] tabState = new boolean[tabPane.getTabCount()];
for(int i=0; i<tabState.length; i++)
{
tabState[i] = tabPane.isEnabledAt(i);
}
entryState = new F2FState(selectedPower, tabState);
}
}// saveState()
/** Restore the state */
public void restoreState(F2FState state)
{
if(turnState != null)
{
setupState(state);
}
else
{
// temporarily save, until
// we are able to restore.
tempState = state;
}
}// restoreState()
/** Get the state, so it may be restored later. */
public F2FState getState()
{
assert(entryState != null);
return entryState;
}// getState()
/** The F2F State, for saving */
public static class F2FState
{
private final boolean[] tabState;
private final Power currentPower;
/** Create an F2FState object */
public F2FState(Power currentPower, boolean[] tabState)
{
if(tabState == null) { throw new IllegalArgumentException(); }
this.currentPower = currentPower;
this.tabState = tabState;
}// F2FState
/** The current power (or null) who is entering orders. */
public Power getCurrentPower() { return currentPower; }
/** The submission state of the displayed tabs. (including the 'all' tab) */
public boolean[] getTabState() { return tabState; }
}// nested class F2FState
}// class F2FOrderDisplayPanel