Index: src/games/strategy/engine/framework/startup/ui/editors/validators/IntegerValidator.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/validators/IntegerValidator.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/validators/IntegerValidator.java	(revision )
@@ -0,0 +1,40 @@
+package games.strategy.engine.framework.startup.ui.editors.validators;
+
+/**
+ * A validator that validates that a string is a number, and within a given min/max range
+ * @author Klaus Groenbaek
+ */
+public class IntegerValidator implements IValidator
+{
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+	private int m_min;
+	private int m_max;
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+
+	public IntegerValidator(int min	, int max)
+	{
+		m_min = min;
+		m_max = max;
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	public boolean isValid(String text)
+	{
+		try
+		{
+			int i = Integer.parseInt(text);
+			return m_min <= i && m_max >= i;
+
+		} catch (NumberFormatException e)
+		{
+			return false;
+		}
+	}
+}
Index: src/games/strategy/engine/pbem/IPBEMTurnSummaryMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/IPBEMTurnSummaryMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/IForumPoster.java	(revision )
@@ -13,9 +13,123 @@
  */
 package games.strategy.engine.pbem;
 
-public interface IPBEMTurnSummaryMessenger extends IPBEMMessenger
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import java.io.File;
+
+/**
+ * An interface for classes that can post a turn summary, the summary may also include a save game if the
+ * implementing class supports this
+ */
+public interface IForumPoster extends IBean
 {
-	public boolean postTurnSummary(String summary, String screenshotRef, String saveGameRef);
+	/**
+	 * Called when the turn summary should be posted
+	 *
+	 *
+	 * @param summary the summary
+	 * @return true if the post was successful
+	 */
+	public boolean postTurnSummary(String summary);
-	
+
+	/**
+	 * Get the reference to the posted turn summary
+	 * @return the reference string, often a URL
+	 */
 	public String getTurnSummaryRef();
+
+	/**
+	 * Should the summary include the save game. When this is true the client must call addSaveGame prior to calling
+	 * postSummary
+	 * @return true if the save game should be included in the summary
+	 */
+	public boolean getIncludeSaveGame();
+
+	/**
+	 * Configure if the save game should be included in the summary post
+	 * @param include true if the save game should be included
+	 */
+	public void setIncludeSaveGame(boolean include);
+
+	/**
+	 * Called to add the save game to the summary, this should only be called if getIncludeSaveGame returns true
+	 *
+	 * @param saveGame the save game file
+	 * @param fileName
+	 */
+	void addSaveGame(File saveGame, String fileName);
+
+	/**
+	 * Create a clone of this object
+	 * @return the clone
+	 */
+	IForumPoster doClone();
+
+
+	/**
+	 * Returns true if this forum poster supports attaching save games
+	 * @return true if save games are supported
+	 */
+	public boolean supportsSaveGame();
+
+	/**
+	 * Can you view the forum post with this poster
+	 * @return
+	 */
+	public boolean getCanViewPosted();
+
+	/**
+	 * Get the user name to login with
+	 * @return
+	 */
+	public String getDisplayName();
+
+
+	/**
+	 * Get the forum id
+	 * @return the forum id
+	 */
+	public String getForumId();
+
+	/**
+	 * get the user name to login
+	 * @return the user name
+	 */
+	public String getUsername();
+
+	/**
+	 * Get the password to login
+ 	 * @return the password
+	 */
+	public String getPassword();
+
+	/**
+	 * Set the forum id
+	 * @param forumId the new forum id
+	 */
+	public void setForumId(String forumId);
+
+	/**
+	 * Set the user name
+	 * @param username the new user name
+	 */
+	public void setUsername(String username);
+
+	/**
+	 * Set the password
+	 * @param password the new password
+	 */
+	public void setPassword(String password);
+
+	/**
+	 * Opens a browser and go to the forum post, identified by the forumId
+	 */
+	public void viewPosted();
+
+	/**
+	 * Remove any sensitive information, like passwords before this object is saved in as a game properties
+	 */
+	void clearSensitiveInfo();
+
+
 }
Index: src/games/strategy/engine/framework/startup/ui/editors/validators/NonEmptyValidator.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/validators/NonEmptyValidator.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/validators/NonEmptyValidator.java	(revision )
@@ -0,0 +1,18 @@
+package games.strategy.engine.framework.startup.ui.editors.validators;
+
+/**
+ * A validator that validates that the text is not empty
+ * @author Klaus Groenbaek
+ */
+public class NonEmptyValidator implements IValidator
+{
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	public boolean isValid(String text)
+	{
+		return text.length() > 0;
+	}
+}
Index: src/games/strategy/engine/framework/startup/ui/editors/SelectAndViewEditor.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/SelectAndViewEditor.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/SelectAndViewEditor.java	(revision )
@@ -0,0 +1,197 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.List;
+
+/**
+ * Allows you put multiple beans in a list and use drop down to select which bean to configure.
+ * The bean's editor is displayed below the dropdown
+ *
+ * @author Klaus Groenbaek
+ */
+public class SelectAndViewEditor extends EditorPanel
+{
+	//-----------------------------------------------------------------------
+	// instance fields 
+	//-----------------------------------------------------------------------
+	JComboBox m_selector = new JComboBox();
+	JPanel m_view = new JPanel();
+	private PropertyChangeListener _properChangeListener;
+	private EditorPanel m_editor;
+	private JLabel m_selectorLabel;
+
+	//-----------------------------------------------------------------------
+	// constructors 
+	//-----------------------------------------------------------------------
+
+	public SelectAndViewEditor(String labelTitle)
+	{
+		super();
+
+		m_view.setLayout(new GridBagLayout());
+
+		m_selectorLabel = new JLabel(labelTitle + ":");
+		add(m_selectorLabel, new GridBagConstraints(0, 0, 1, 1, 0d, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 1, 2), 0, 0));
+		add(m_selector, new GridBagConstraints(1, 0, 1, 1, 1.0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 1, 0), 0, 0));
+
+		add(m_view, new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+
+		m_selector.setRenderer(new DisplayNameComboBoxRender());
+		m_selector.addItemListener(new ItemListener()
+		{
+			public void itemStateChanged(ItemEvent e)
+			{
+				if (e.getStateChange() == ItemEvent.SELECTED)
+				{
+					updateView();
+					notifyListeners();
+				}
+			}
+		});
+
+		_properChangeListener = new PropertyChangeListener()
+		{
+			public void propertyChange(PropertyChangeEvent evt)
+			{
+				notifyListeners();
+			}
+		};
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	private void updateView()
+	{
+		// todo(kg) Have the View use a card layout instead of removing all content
+		// remove listeners from old editor, to avoid memory leak
+		if (m_editor != null)
+		{
+			m_editor.removePropertyChangeListener(_properChangeListener);
+		}
+
+		//
+		m_view.removeAll();
+		IBean item = (IBean) m_selector.getSelectedItem();
+		m_editor = item.getEditor();
+		if (m_editor != null)
+		{
+			// register a property change listener so we can re-notify our listeners
+			m_editor.addPropertyChangeListener(_properChangeListener);
+			m_view.add(m_editor, new GridBagConstraints(0, 0, 1, 1, 1.0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+			m_editor.isInputValid();
+		}
+		revalidate();
+
+		alignLabels();
+
+
+	}
+
+	/**
+	 * Aligns label of this editor with the nested editor, by either resizing this (if it is smaller)
+	 * or resizing the labels on the nested editor (if it is bigger)
+	 */
+	private void alignLabels()
+	{
+		// resize the label to align with the nested editors labels
+		int height = m_selectorLabel.getPreferredSize().height;
+		int width = m_selectorLabel.getPreferredSize().width;
+		if (m_editor != null)
+		{
+			int labelWidth = m_editor.getLabelWidth();
+			if (width < labelWidth)
+			{
+				// resize this editors label
+				width = labelWidth;
+			}
+			else {
+				// resize nested editors labels
+				m_editor.setLabelWidth(width);
+			}
+		}
+		Dimension dimension = new Dimension(width, height);
+		m_selectorLabel.setPreferredSize(dimension);
+		m_selectorLabel.setSize(dimension);
+	}
+
+	/**
+	 * Sets the list of possible beans to choose from
+	 *
+	 * @param beans the list of beans
+	 */
+	public void setBeans(List<? extends IBean> beans)
+	{
+		m_selector.setModel(new DefaultComboBoxModel(beans.toArray()));
+		updateView();
+	}
+
+	/**
+	 * Notify objects listening to this editor
+	 */
+	private void notifyListeners()
+	{
+		firePropertyChange(EDITOR_CHANGE, null, null);
+	}
+
+
+	@Override
+	public boolean isInputValid()
+	{
+		return m_editor == null || m_editor.isInputValid();
+	}
+
+	@Override
+	public IBean getBean()
+	{
+		if (m_editor == null) {
+			return null;
+		}
+		return m_editor.getBean();
+	}
+
+	/**
+	 * Sets the bean on this editor.
+	 * If an editor of the same class is found, it is selected an modified to match
+	 * If no bean of this type is found, it is added to the list
+	 *
+	 * @param bean the bean
+	 */
+	public void setSelectedBean(IBean bean)
+	{
+		DefaultComboBoxModel model = (DefaultComboBoxModel) m_selector.getModel();
+		DefaultComboBoxModel newModel = new DefaultComboBoxModel();
+		boolean found = false;
+		int i;
+		for (i = 0; i < model.getSize(); i++)
+		{
+			IBean candidate = (IBean) model.getElementAt(i);
+			if (candidate.sameType(bean))
+			{
+				found = true;
+				newModel.addElement(bean);
+			} else
+			{
+				newModel.addElement(candidate);
+			}
+		}
+
+		if (found)
+		{
+			m_selector.setModel(newModel);
+		} else
+		{
+			model.addElement(bean);
+		}
+		m_selector.setSelectedItem(bean);
+		updateView();
+	}
+
+
+}
Index: src/games/strategy/engine/framework/startup/LocalBeanCache.java
===================================================================
--- src/games/strategy/engine/framework/startup/LocalBeanCache.java	(revision )
+++ src/games/strategy/engine/framework/startup/LocalBeanCache.java	(revision )
@@ -0,0 +1,169 @@
+package games.strategy.engine.framework.startup;
+
+import games.strategy.engine.framework.GameRunner;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A cache for serialized beans that should be stored locally.
+ * This is used to store settings which are not game related, and should therefore not go into the options cache
+ * This is often used by editors to remember previous values
+ *
+ * @author Klaus Groenbaek
+ */
+public class LocalBeanCache
+{
+	//-----------------------------------------------------------------------
+	// class fields
+	//-----------------------------------------------------------------------
+	private static final LocalBeanCache s_INSTANCE = new LocalBeanCache();
+	private File m_file;
+	private final Object m_mutex = new Object();
+
+
+	//-----------------------------------------------------------------------
+	// class methods
+	//-----------------------------------------------------------------------
+	public static LocalBeanCache getInstance()
+	{
+		return s_INSTANCE;
+	}
+
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+	Map<String, IBean> m_map = new HashMap<String, IBean>();
+
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+
+	private LocalBeanCache()
+	{
+		m_file = new File(GameRunner.getUserRootFolder(), "local.cache");
+		m_map = loadMap();
+
+		// add a shutdown, just in case someone forgets to call writeToDisk
+		Thread shutdown = new Thread(new Runnable()
+		{
+			public void run()
+			{
+				writeToDisk();
+			}
+		});
+		Runtime.getRuntime().addShutdownHook(shutdown);
+
+	}
+
+	@SuppressWarnings("unchecked")
+	private Map<String, IBean> loadMap()
+	{
+
+		if (m_file.exists())
+		{
+			try
+			{
+				ObjectInput oin = null;
+				try
+				{
+					oin = new ObjectInputStream(new FileInputStream(m_file));
+					Object o = oin.readObject();
+					if (o instanceof Map)
+					{
+						Map m = (Map) o;
+						for (Object o1 : m.keySet())
+						{
+							if (!(o1 instanceof String))
+							{
+								throw new Exception("Map is corrupt");
+							}
+						}
+					} else
+					{
+						throw new Exception("File is corrupt");
+					}
+					// we know that the map has proper type key/value
+					return (HashMap<String, IBean>) o;
+				} finally
+				{
+					if (oin != null)
+					{
+						// close stream, or we can delete the file (on windows)
+						oin.close();
+					}
+				}
+			} catch (Exception e)
+			{
+				// on error we delete the cache file, if we can
+				m_file.delete();
+				System.err.println("Serialization cache invalid");
+			}
+		}
+		return new HashMap<String, IBean>();
+
+	}
+
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	/**
+	 * adds a new Serializable to the cache
+	 *
+	 * @param key		  the key the serializable should be stored under. Take care not to override a serializable stored by other code
+	 *                     it is generally a good ide to use fully qualified class names, getClass().getCanonicalName() as key
+	 * @param bean the bean
+	 */
+	public void storeSerializable(String key, IBean bean)
+	{
+		m_map.put(key, bean);
+	}
+
+	/**
+	 * Call to have the cache written to disk
+	 */
+	public void writeToDisk()
+	{
+		synchronized (m_mutex)
+		{
+
+			ObjectOutputStream out = null;
+			try
+			{
+				out = new ObjectOutputStream(new FileOutputStream(m_file, false));
+				out.writeObject(m_map);
+
+			} catch (IOException e)
+			{
+				// ignore
+			} finally
+			{
+				if (out != null)
+				{
+					try
+					{
+						out.close();
+					} catch (IOException e)
+					{
+						// ignore
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get a serializable from the cache
+	 *
+	 * @param key the key ot was stored under
+	 * @return the serializable or null if one doesn't exists under the given key
+	 */
+	public IBean getSerializable(String key)
+	{
+		return m_map.get(key);
+	}
+}
Index: src/games/strategy/kingstable/KingsTable.java
===================================================================
--- src/games/strategy/kingstable/KingsTable.java	(revision 3313)
+++ src/games/strategy/kingstable/KingsTable.java	(revision )
@@ -21,8 +21,6 @@
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.AllYouCanUploadDotComPBEMMessenger;
-import games.strategy.engine.pbem.IPBEMMessenger;
 import games.strategy.kingstable.player.BetterAI;
 import games.strategy.kingstable.player.IKingsTablePlayer;
 import games.strategy.kingstable.player.KingsTablePlayer;
@@ -31,14 +29,13 @@
 import games.strategy.kingstable.ui.display.IKingsTableDisplay;
 import games.strategy.kingstable.ui.display.KingsTableDisplay;
 
+import javax.swing.*;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
-import javax.swing.SwingUtilities;
-
 /**
  * Main class responsible for a Kings Table game.
  * 
@@ -64,9 +61,7 @@
 	public static boolean edgeOfBoardCanBeUsedToCaptureTheKing;
 	public static boolean kingCanBeCapturedLikeAPawn;
 	
-	/**
-	 * @see IGameLoader.createPlayers(playerNames)
-	 */
+
 	public Set<IGamePlayer> createPlayers(final Map<String, String> playerNames)
 	{
 		final Set<IGamePlayer> players = new HashSet<IGamePlayer>();
@@ -179,11 +174,7 @@
 		}
 	}
 	
-	public IPBEMMessenger[] getPBEMMessengers()
-	{
-		return new IPBEMMessenger[] { new AllYouCanUploadDotComPBEMMessenger() };
-	}
-	
+
 	/**
 	 * @see games.strategy.engine.framework.IGameLoader#getDisplayType()
 	 */
Index: src/games/strategy/engine/framework/startup/launcher/LocalLauncher.java
===================================================================
--- src/games/strategy/engine/framework/startup/launcher/LocalLauncher.java	(revision 3313)
+++ src/games/strategy/engine/framework/startup/launcher/LocalLauncher.java	(revision )
@@ -25,15 +25,13 @@
 import games.strategy.net.IServerMessenger;
 import games.strategy.net.Messengers;
 
-import java.awt.Component;
+import javax.swing.*;
+import java.awt.*;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.logging.Logger;
 
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
 public class LocalLauncher implements ILauncher
 {
 	private static final Logger s_logger = Logger.getLogger(ILauncher.class.getName());
@@ -109,6 +107,7 @@
 					}
 				} finally
 				{
+					//todo(kg), this does not occur on the swing thread, and this notifies setupPanel observers
 					m_gameSelectorModel.loadDefaultGame(parent);
 					SwingUtilities.invokeLater(new Runnable()
 					{
Index: src/games/strategy/engine/data/properties/GameProperties.java
===================================================================
--- src/games/strategy/engine/data/properties/GameProperties.java	(revision 3372)
+++ src/games/strategy/engine/data/properties/GameProperties.java	(revision )
@@ -23,7 +23,6 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -63,6 +62,7 @@
 	 * @param value
 	 *            property
 	 */
+	// todo should only accept serializable, not object
 	public void set(final String key, final Object value)
 	{
 		if (value == null)
Index: src/games/strategy/engine/random/PBEMDiceRoller.java
===================================================================
--- src/games/strategy/engine/random/PBEMDiceRoller.java	(revision 3313)
+++ src/games/strategy/engine/random/PBEMDiceRoller.java	(revision )
@@ -13,9 +13,8 @@
  */
 package games.strategy.engine.random;
 
-import java.awt.BorderLayout;
-import java.awt.Frame;
-import java.awt.Window;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.IOException;
@@ -25,27 +24,16 @@
 import java.net.SocketException;
 import java.util.concurrent.atomic.AtomicReference;
 
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.SwingUtilities;
-import javax.swing.WindowConstants;
-
 /**
- * Rolls the dice using http://www.irony.com/mailroll.html
- * 
+ *
  * Its a bit messy, but the threads are a pain to deal with We want to be able
  * to call this from any thread, and have a dialog that doesnt close until the
  * dice roll finishes. If there is an error we wait until we get a good roll
  * before returning.
+ * @author George_H
  */
 public class PBEMDiceRoller implements IRandomSource
 {
-	private final String m_player1Email;
-	private final String m_player2Email;
-	private final String m_gameID;
 	private final String m_gameUUID;
 	private final IRemoteDiceServer m_remoteDiceServer;
 	private static Frame s_focusWindow;
@@ -61,11 +49,8 @@
 		s_focusWindow = w;
 	}
 	
-	public PBEMDiceRoller(final String player1Email, final String player2Email, final String gameID, final IRemoteDiceServer diceServer, final String gameUUID)
+	public PBEMDiceRoller( final IRemoteDiceServer diceServer, final String gameUUID)
 	{
-		m_player1Email = player1Email;
-		m_player2Email = player2Email;
-		m_gameID = gameID;
 		m_remoteDiceServer = diceServer;
 		m_gameUUID = gameUUID;
 	}
@@ -76,7 +61,7 @@
 	public void test()
 	{
 		// TODO: do a test based on data.getDiceSides()
-		final HttpDiceRollerDialog dialog = new HttpDiceRollerDialog(getFocusedFrame(), 6, 1, "Test", m_player1Email, m_player2Email, m_gameID, m_remoteDiceServer, "test-roll");
+		final HttpDiceRollerDialog dialog = new HttpDiceRollerDialog(getFocusedFrame(), 6, 1, "Test", m_remoteDiceServer, "test-roll");
 		dialog.setTest();
 		dialog.roll();
 	}
@@ -107,7 +92,7 @@
 			}
 			return result.get();
 		}
-		final HttpDiceRollerDialog dialog = new HttpDiceRollerDialog(getFocusedFrame(), max, count, annotation, m_player1Email, m_player2Email, m_gameID, m_remoteDiceServer, m_gameUUID);
+		final HttpDiceRollerDialog dialog = new HttpDiceRollerDialog(getFocusedFrame(), max, count, annotation, m_remoteDiceServer, m_gameUUID);
 		dialog.roll();
 		return dialog.getDiceRoll();
 	}
@@ -149,7 +134,9 @@
 	}
 }
 
-
+/**
+ * The dialog that will show while the dice are rolling
+ */
 class HttpDiceRollerDialog extends JDialog
 {
 	private final JButton m_exitButton = new JButton("Exit");
@@ -158,10 +145,9 @@
 	private final JTextArea m_text = new JTextArea();
 	private int[] m_diceRoll;
 	private final int m_count;
-	private final int m_max;
-	private final String m_annotation;
-	private final String m_email1;
-	private final String m_email2;
+	private final int m_sides;
+	private final String m_subjectMessage;
+
 	private final String m_gameID;
 	private final IRemoteDiceServer m_diceServer;
 	private final String m_gameUUID;
@@ -169,18 +155,25 @@
 	public boolean m_test = false;
 	private final JPanel m_buttons = new JPanel();
 	private final Window m_owner;
-	
+
-	public HttpDiceRollerDialog(final Frame owner, final int max, final int count, final String annotation, final String email1, final String email2, final String gameID,
+	/**
+	 *
+	 * @param owner owner frame
+	 * @param sides the number of sides on the dice
+	 * @param count the number of dice rolled
+	 * @param subjectMessage the subject for the email the dice roller will send (if it sends emails)
+	 * @param diceServer the dice server implementation
+	 * @param gameUUID the TripleA game UUID or null
+	 */
+	public HttpDiceRollerDialog(final Frame owner, final int sides, final int count, final String subjectMessage,
 				final IRemoteDiceServer diceServer, final String gameUUID)
 	{
 		super(owner, "Dice roller", true);
 		m_owner = owner;
-		m_max = max;
+		m_sides = sides;
 		m_count = count;
-		m_annotation = annotation;
-		m_email1 = email1;
-		m_email2 = email2;
-		m_gameID = gameID;
+		m_subjectMessage = subjectMessage;
+		m_gameID = diceServer.getGameId() == null ? "" : diceServer.getGameId();
 		m_diceServer = diceServer;
 		m_gameUUID = gameUUID;
 		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
@@ -324,25 +317,27 @@
 	/**
 	 * should be called from a thread other than the event thread after we are
 	 * open (or at least in the process of opening) will close the window and
-	 * notify any waiting threads when completed succeddfully.
+	 * notify any waiting threads when completed successfully.
 	 * 
 	 * Before contacting Irony Dice Server, check if email has a reasonable
 	 * valid syntax.
 	 * 
-	 * @author George_H
+	 *
 	 */
 	private void rollInSeperateThread()
 	{
 		if (SwingUtilities.isEventDispatchThread())
 			throw new IllegalStateException("Wrong thread");
 		while (!isVisible())
+		{
 			Thread.yield();
-		appendText(m_annotation + "\n");
-		appendText("Contacting  " + m_diceServer.getName() + "\n");
+		}
+		appendText(m_subjectMessage + "\n");
+		appendText("Contacting  " + m_diceServer.getDisplayName() + "\n");
 		String text = null;
 		try
 		{
-			text = m_diceServer.postRequest(m_email1, m_email2, m_max, m_count, m_annotation, m_gameID, m_gameUUID);
+			text = m_diceServer.postRequest(m_sides, m_count, m_subjectMessage, m_gameID, m_gameUUID);
 			if (text.length() == 0)
 			{
 				appendText("Nothing could be read from dice server\n");
@@ -364,10 +359,7 @@
 		} catch (final InvocationTargetException e)
 		{
 			appendText("\nError:" + e.getMessage() + "\n\n");
-			if (text != null)
-			{
-				appendText("Text from dice server:\n" + text + "\n");
+			appendText("Text from dice server:\n" + text + "\n");
-			}
 			notifyError();
 		} catch (final IOException ex)
 		{
Index: src/games/strategy/engine/pbem/HotmailEmailSender.java
===================================================================
--- src/games/strategy/engine/pbem/HotmailEmailSender.java	(revision )
+++ src/games/strategy/engine/pbem/HotmailEmailSender.java	(revision )
@@ -0,0 +1,59 @@
+package games.strategy.engine.pbem;
+
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.EmailSenderEditor;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+/**
+ * A pre configured Email sender that uses Hotmail's SMTP server
+ *
+ * @author Klaus Groenbaek
+ */
+public class HotmailEmailSender extends GenericEmailSender
+{
+	private static final long serialVersionUID = 3511375113962472063L;
+
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+
+	public HotmailEmailSender()
+	{
+		setHost("smtp.live.com");
+		setPort(587);
+		setEncryption(Encryption.TLS);
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	@Override
+	public EditorPanel getEditor()
+	{
+		return new EmailSenderEditor(this, new EmailSenderEditor.EditorConfiguration());
+	}
+
+	@Override
+	public String getDisplayName()
+	{
+		return "Hotmail ";
+	}
+
+	@Override
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == HotmailEmailSender.class;
+	}
+
+	@Override
+	public IEmailSender doClone()
+	{
+		GenericEmailSender sender = new HotmailEmailSender();
+		sender.setSubjectPrefix(getSubjectPrefix());
+		sender.setPassword(getPassword());
+		sender.setToAddress(getToAddress());
+		sender.setUserName(getUserName());
+		return sender;
+	}
+}
Index: src/games/puzzle/tictactoe/TicTacToe.java
===================================================================
--- src/games/puzzle/tictactoe/TicTacToe.java	(revision 3313)
+++ src/games/puzzle/tictactoe/TicTacToe.java	(revision )
@@ -27,17 +27,14 @@
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.AllYouCanUploadDotComPBEMMessenger;
-import games.strategy.engine.pbem.IPBEMMessenger;
 
+import javax.swing.*;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
-import javax.swing.SwingUtilities;
-
 /**
  * Main class responsible for a Tic Tac Toe game.
  * 
@@ -54,9 +51,7 @@
 	private static final String MINIMAX_COMPUTER_PLAYER_TYPE = "Minimax AI";
 	private static final String ALPHABETA_COMPUTER_PLAYER_TYPE = "\u03B1\u03B2 AI";// "?? AI";
 	
-	/**
-	 * @see IGameLoader.createPlayers(playerNames)
-	 */
+
 	public Set<IGamePlayer> createPlayers(final Map<String, String> playerNames)
 	{
 		final Set<IGamePlayer> players = new HashSet<IGamePlayer>();
@@ -160,11 +155,6 @@
 		}
 	}
 	
-	public IPBEMMessenger[] getPBEMMessengers()
-	{
-		return new IPBEMMessenger[] { new AllYouCanUploadDotComPBEMMessenger() };
-	}
-	
 	/**
 	 * @see games.strategy.engine.framework.IGameLoader#getDisplayType()
 	 */
Index: src/games/strategy/engine/random/IRemoteDiceServer.java
===================================================================
--- src/games/strategy/engine/random/IRemoteDiceServer.java	(revision 3313)
+++ src/games/strategy/engine/random/IRemoteDiceServer.java	(revision )
@@ -1,14 +1,16 @@
 package games.strategy.engine.random;
 
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 
-public interface IRemoteDiceServer
+public interface IRemoteDiceServer extends IBean
 {
 	/**
 	 * Post a request to the dice server, and return the resulting html page as a string
 	 */
-	public String postRequest(String player1, String player2, int max, int numDice, String text, String gameID, String gameUUID) throws IOException;
+	public String postRequest(int max, int numDice, String subjectMessage, String gameID, String gameUUID) throws IOException;
 	
 	/**
 	 * Given the html page returned from postRequest, return the dice []
@@ -18,7 +20,58 @@
 	public int[] getDice(String string, int count) throws IOException, InvocationTargetException;
 	
 	/**
-	 * Get the display name for this dice server
+	 * Get the to address
+	 * @return returns the to address or null if not configured
 	 */
-	public String getName();
+	String getToAddress();
+
+	/**
+	 * Set the to address
+	 * @param toAddress the new to address
+	 */
+	void setToAddress(String toAddress);
+
+	/**
+	 * get the CC address
+	 * @return the address or null if not configured
+	 */
+	String getCcAddress();
+
+	/**
+	 * Set the cc address
+	 * @param ccAddress the address or null if not configured
+	 */
+	void setCcAddress(String ccAddress);
+
+	/**
+	 * Get the info text displayed for this Dice server
+	 * @return the info text
+	 */
+	String getInfoText();
+
+	/**
+	 * True if this dice server sends email, and therefore requires email addresses
+	 * @return true if email addresses are required
+	 */
+	boolean sendsEmail();
+
+	/**
+	 * True if this dice server requires a game id
+	 * @return true if a game id is required
+	 */
+	boolean supportsGameId();
+
+	/**
+	 * Set the game id used for this instance.
+	 * If you don't provide a game id, the TripleA GUID will be used
+	 * @param gameId the new game id
+	 */
+	public void setGameId(String gameId);
+
+	/**
+	 * Get the configured game id
+	 * @return the game id or null if not configured
+	 */
+	public String getGameId();
+
 }
Index: src/games/strategy/engine/framework/startup/ui/editors/EmailSenderEditor.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/EmailSenderEditor.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/EmailSenderEditor.java	(revision )
@@ -0,0 +1,294 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+
+import games.strategy.engine.framework.GameRunner;
+import games.strategy.engine.framework.startup.ui.MainFrame;
+import games.strategy.engine.framework.startup.ui.editors.validators.EmailValidator;
+import games.strategy.engine.framework.startup.ui.editors.validators.IntegerValidator;
+import games.strategy.engine.pbem.GenericEmailSender;
+import games.strategy.engine.pbem.IEmailSender;
+import games.strategy.ui.ProgressWindow;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * An editor for modifying email senders
+ *
+ * @author Klaus Groenbaek
+ */
+public class EmailSenderEditor extends EditorPanel
+{
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+
+	private GenericEmailSender m_bean;
+	private JTextField m_subject = new JTextField();
+
+	private JTextField m_toAddress = new JTextField();
+	private JTextField m_host = new JTextField();
+	private JTextField m_port = new JTextField();
+	private JTextField m_login = new JTextField();
+	private JCheckBox m_useTLS = new JCheckBox("Use TLS encryption");
+	private JTextField m_password = new JPasswordField();
+	private JLabel m_toLabel = new JLabel("To:");
+	private JLabel m_loginLabel = new JLabel("Login:");
+	private JLabel m_passwordLabel = new JLabel("Password:");
+	private JLabel m_hostLabel = new JLabel("Host:");
+	private JLabel m_portLabel = new JLabel("Port:");
+	private final JButton m_testEmail = new JButton("Test Email");
+
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+	public EmailSenderEditor(GenericEmailSender bean, EditorConfiguration editorConfiguration)
+	{
+		super();
+		m_bean = bean;
+
+		m_subject.setText(m_bean.getSubjectPrefix());
+		m_host.setText(m_bean.getHost());
+		m_port.setText(String.valueOf(m_bean.getPort()));
+		m_toAddress.setText(m_bean.getToAddress());
+		m_login.setText(m_bean.getUserName());
+		m_password.setText(m_bean.getPassword());
+		m_useTLS.setSelected(m_bean.getEncryption() == GenericEmailSender.Encryption.TLS);
+
+		int bottomSpace = 1;
+		int labelSpace = 2;
+		int row = 0;
+
+		add(new JLabel("Subject:"), new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+		add(m_subject, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+
+		row++;
+		add(m_toLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+		add(m_toAddress, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+
+		row++;
+		add(m_loginLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+		add(m_login, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+
+		row++;
+		add(m_passwordLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+		add(m_password, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+
+		if (editorConfiguration.showHost) {
+			row++;
+			add(m_hostLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+			add(m_host, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+		}
+
+		if (editorConfiguration.showPort) {
+			row++;
+			add(m_portLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,labelSpace), 0, 0));
+			add(m_port, new GridBagConstraints(1, row, 2, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0,0,bottomSpace,0), 0, 0));
+		}
+
+		if (editorConfiguration.showEncryption) {
+			row++;
+			add(m_useTLS, new GridBagConstraints(0, row, 2, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,0), 0, 0));
+			// add Test button on the same line as encryption
+			add(m_testEmail, new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,0), 0, 0));
+
+		} else {
+			row++;
+			// or on a separate line if no encryption
+			add(m_testEmail, new GridBagConstraints(1, row, 3, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0,0,bottomSpace,0), 0, 0));
+		}
+
+		setupListeners();
+	}
+
+	private void setupListeners()
+	{
+		ValidateInputOnChangeListener listener = new ValidateInputOnChangeListener();
+		m_host.getDocument().addDocumentListener(listener);
+		m_login.getDocument().addDocumentListener(listener);
+		m_port.getDocument().addDocumentListener(listener);
+		m_password.getDocument().addDocumentListener(listener);
+		m_toAddress.getDocument().addDocumentListener(listener);
+		m_useTLS.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(ActionEvent e)
+			{
+			 notifyListeners();
+			}
+		});
+
+		m_testEmail.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(ActionEvent e)
+			{
+				testEmail();
+			}
+		});
+
+	}
+
+	/**
+	 * Tests the email sender. This must be called from the swing event thread
+	 */
+	private void testEmail()
+	{
+		final ProgressWindow progressWindow = new ProgressWindow(MainFrame.getInstance(), "Sending test email...");
+		progressWindow.setVisible(true);
+
+		Runnable runnable = new Runnable()
+		{
+			public void run()
+			{
+				// initialize variables to error state, override if successful
+				String message = "An unknown occurred, report this as a bug on the TripleA dev forum";
+				int messageType = JOptionPane.ERROR_MESSAGE;
+				try
+				{
+					String html = "<html><body><h1>Success</h1><p>This was a test email sent by TripleA<p></body></html>";
+					File dummy = new File(GameRunner.getUserRootFolder(), "dummySave.txt");
+					dummy.deleteOnExit();
+					FileOutputStream fout = new FileOutputStream(dummy);
+					fout.write("This file would normally be a save game".getBytes());
+					fout.close();
+
+					m_bean.sendEmail("TripleA Test", html, dummy, "dummy.txt");
+					// email was sent, or an exception would have been thrown
+					message = "Email sent, it should arrive shortly, otherwise check your spam folder";
+					messageType = JOptionPane.INFORMATION_MESSAGE;
+
+				} catch (final IOException ioe)
+				{
+					message = "Unable to send email: " + ioe.getMessage();
+
+				} finally
+				{
+					// now that we have a result, marshall it back unto the swing thread
+					final String finalMessage = message;
+					final int finalMessageType = messageType;
+					SwingUtilities.invokeLater(new Runnable()
+					{
+						public void run()
+						{
+							try
+							{
+								JOptionPane.showMessageDialog(MainFrame.getInstance(), finalMessage, "Email Test", finalMessageType);
+							} catch (HeadlessException e)
+							{
+								// should never happen in a GUI app
+							}
+						}
+					});
+					progressWindow.setVisible(false);
+				}
+
+			}
+		};
+		// start a background thread
+		Thread t = new Thread(runnable);
+		t.start();
+	}
+
+	public boolean isInputValid() {
+
+		boolean hostValid = validateTextField(m_host, m_hostLabel);
+		boolean portValid = validateTextField(m_port, m_portLabel, new IntegerValidator(0, 65635));
+		boolean loginValid = validateTextField(m_login, m_loginLabel);
+		boolean passwordValid = validateTextField(m_password, m_passwordLabel);
+		boolean addressValid = validateTextField(m_toAddress, m_toLabel, new EmailValidator(false));
+
+		boolean allValid = hostValid && portValid && loginValid && passwordValid && addressValid;
+		m_testEmail.setEnabled(allValid);
+		return allValid;
+	}
+
+	@Override
+	public IBean getBean()
+	{
+		return getEmailSender();
+	}
+
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	public IEmailSender getEmailSender()
+	{
+		m_bean.setEncryption(m_useTLS.isSelected() ? GenericEmailSender.Encryption.TLS : GenericEmailSender.Encryption.NONE);
+		m_bean.setSubjectPrefix(m_subject.getText());
+		m_bean.setHost(m_host.getText());
+		m_bean.setUserName(m_login.getText());
+		m_bean.setPassword(m_password.getText());
+		int port = 0;
+		try
+		{
+			port = Integer.parseInt(m_port.getText());
+		} catch (NumberFormatException e)
+		{
+			// ignore
+		}
+		m_bean.setPort(port);
+		m_bean.setToAddress(m_toAddress.getText());
+		return m_bean;
+	}
+
+	private void notifyListeners()
+	{
+		firePropertyChange(EDITOR_CHANGE, null, null);
+	}
+
+
+	//-----------------------------------------------------------------------
+	// inner classes
+	//-----------------------------------------------------------------------
+
+	public static class EditorConfiguration {
+		public boolean showHost;
+		public boolean showPort;
+		public boolean showEncryption;
+
+		public EditorConfiguration()
+		{
+		}
+
+		public EditorConfiguration(boolean showHost, boolean showPort, boolean showEncryption)
+		{
+			this.showHost = showHost;
+			this.showPort = showPort;
+			this.showEncryption = showEncryption;
+		}
+	}
+
+
+	/**
+	 * Document listener which calls validateAndEnableInput in response to any document change
+	 */
+	private class ValidateInputOnChangeListener implements DocumentListener
+	{
+		public void changedUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void insertUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void removeUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+	}
+
+
+
+}
Index: src/games/strategy/engine/pbem/AllYouCanUploadDotComPBEMMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/AllYouCanUploadDotComPBEMMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/AllYouCanUploadDotComPBEMMessenger.java	(revision 3313)
@@ -1,179 +0,0 @@
-/*
- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-/*
- * AllYouCanUploadDotComPBEMMessenger.java
- * 
- * 
- * Created on November 21, 2006, 8:56 PM
- */
-package games.strategy.engine.pbem;
-
-import games.strategy.net.MultiPartFormOutputStream;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @author Tony Clayton
- * @version 1.0
- */
-public class AllYouCanUploadDotComPBEMMessenger implements IPBEMScreenshotMessenger
-{
-	private transient String m_screenshotRef = null;
-	
-	public AllYouCanUploadDotComPBEMMessenger()
-	{
-	}
-	
-	public String getName()
-	{
-		return "www.AllYouCanUpload.com";
-	}
-	
-	public boolean getNeedsUsername()
-	{
-		return false;
-	}
-	
-	public boolean getNeedsPassword()
-	{
-		return false;
-	}
-	
-	public boolean getCanViewPosted()
-	{
-		return false;
-	}
-	
-	public void viewPosted()
-	{
-	}
-	
-	public void setGameId(final String gameId)
-	{
-	}
-	
-	public String getGameId()
-	{
-		return null;
-	}
-	
-	public void setUsername(final String username)
-	{
-	}
-	
-	public String getUsername()
-	{
-		return null;
-	}
-	
-	public void setPassword(final String password)
-	{
-	}
-	
-	public String getPassword()
-	{
-		return null;
-	}
-	
-	public boolean postScreenshot(final String fileName, final InputStream fileIn) throws IOException
-	{
-		URL url = null;
-		URLConnection urlConn = null;
-		MultiPartFormOutputStream out = null;
-		m_screenshotRef = null;
-		// set up connection
-		try
-		{
-			url = new URL("http://allyoucanupload.webshots.com/uploadcomplete");
-		} catch (final MalformedURLException e)
-		{
-			e.printStackTrace();
-			return false;
-		}
-		try
-		{
-			final String boundary = MultiPartFormOutputStream.createBoundary();
-			urlConn = MultiPartFormOutputStream.createConnection(url);
-			urlConn.setRequestProperty("Accept", "*/*");
-			urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
-			urlConn.setRequestProperty("Connection", "Keep-Alive");
-			urlConn.setRequestProperty("Cache-Control", "no-cache");
-			((HttpURLConnection) urlConn).setInstanceFollowRedirects(true);
-			out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
-			out.writeField("imagesCount", "1");
-			out.writeField("images[0].submittedPhotoSize", "100%");
-		} catch (final Exception e)
-		{
-			e.printStackTrace();
-			return false;
-		}
-		// this one throws an exception
-		out.writeFile("images[0].fileName", "image/png", fileName, fileIn);
-		// send request to server
-		out.close();
-		final int code = ((HttpURLConnection) urlConn).getResponseCode();
-		if (code != 200)
-		{
-			// http error
-			final String msg = ((HttpURLConnection) urlConn).getResponseMessage();
-			m_screenshotRef = String.valueOf(code) + ": " + msg;
-			return false;
-		}
-		// read response from server
-		try
-		{
-			final BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
-			String line = "";
-			// server will always serve images in .jpg format
-			final Pattern p = Pattern.compile(".*?<input type=\"text\" .*?value=\"(http://.*?jpg)\">.*");
-			while ((line = in.readLine()) != null)
-			{
-				final Matcher m = p.matcher(line);
-				if (m.matches())
-					m_screenshotRef = m.group(1);
-			}
-			in.close();
-		} catch (final IOException ioe)
-		{
-			ioe.printStackTrace();
-			return false;
-		}
-		if (m_screenshotRef == null)
-		{
-			m_screenshotRef = "Error: screenshot URL could not be found after posting.";
-			return false;
-		}
-		return true;
-	}
-	
-	public String getScreenshotRef()
-	{
-		return m_screenshotRef;
-	}
-	
-	@Override
-	public String toString()
-	{
-		return getName();
-	}
-}
Index: src/games/strategy/engine/framework/startup/ui/editors/EditorPanel.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/EditorPanel.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/EditorPanel.java	(revision )
@@ -0,0 +1,111 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import games.strategy.engine.framework.startup.ui.editors.validators.NonEmptyValidator;
+import games.strategy.engine.framework.startup.ui.editors.validators.IValidator;
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.PropertyChangeListener;
+
+/**
+ * Base class for editors, contains a number of utility methods, suce a validation
+ *
+ * @author Klaus Groenbaek
+ */
+public abstract class EditorPanel extends JPanel
+{
+	public static final String EDITOR_CHANGE = "EditorChange";
+	//-----------------------------------------------------------------------
+	// instance fields 
+	//-----------------------------------------------------------------------
+	protected final Color m_labelColor;
+
+	//-----------------------------------------------------------------------
+	// constructor
+	//-----------------------------------------------------------------------
+	public EditorPanel()
+	{
+		super(new GridBagLayout());
+		m_labelColor = new JLabel().getForeground();
+
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	@Override
+	public void addPropertyChangeListener(PropertyChangeListener listener)
+	{
+		super.addPropertyChangeListener(EDITOR_CHANGE, listener);
+	}
+
+	protected boolean validateTextField(JTextField field, JLabel label)
+	{
+		return validateTextField(field, label, new NonEmptyValidator());
+	}
+
+	protected boolean validateTextField(JTextField field, JLabel label, IValidator IValidator)
+	{
+	  boolean valid = true;
+		if (!IValidator.isValid(field.getText()))
+		{
+			valid = false;
+			label.setForeground(Color.RED);
+		} else
+		{
+			label.setForeground(m_labelColor);
+		}
+		return valid;
+
+	}
+
+
+	public abstract boolean isInputValid();
+
+	public abstract IBean getBean();
+
+
+	/**
+	 * Returns the Label width, this can be used by wrapping editors to try to align label sizes
+	 * @return the size of the largest label in the first column
+	 */
+	public int getLabelWidth() {
+		int width = 0;
+		GridBagLayout layout = (GridBagLayout) getLayout();
+		Component[] components = getComponents();
+		for (Component component : components)
+		{
+			// label in first column
+			if (component instanceof JLabel && layout.getConstraints(component).gridx == 0)
+			{
+				if (component.getPreferredSize().width > width) {
+					width = component.getPreferredSize().width;
+				}
+			}
+		}
+		return width;
+	}
+
+	protected boolean emptyOrNull(String str)
+	{
+		return str == null || str.trim().equals("");
+	}
+
+	public void setLabelWidth(int width)
+	{
+		GridBagLayout layout = (GridBagLayout) getLayout();
+		Component[] components = getComponents();
+		for (Component component : components)
+		{
+			// label in first column
+			if (component instanceof JLabel && layout.getConstraints(component).gridx == 0)
+			{
+			    int height = component.getPreferredSize().height;
+				Dimension dimension = new Dimension(width, height);
+				component.setPreferredSize(dimension);
+				component.setSize(dimension);
+			}
+		}
+	}
+}
Index: src/games/strategy/engine/framework/startup/ui/editors/DisplayNameComboBoxRender.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/DisplayNameComboBoxRender.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/DisplayNameComboBoxRender.java	(revision )
@@ -0,0 +1,18 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Combobox render used for beans which have a displayname
+ */
+class DisplayNameComboBoxRender extends DefaultListCellRenderer
+{
+	public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
+	{
+		super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+		   IBean bean = (IBean) value;
+		setText(bean.getDisplayName());
+		return this;
+	}
+}
Index: src/games/strategy/engine/pbem/PBEMMessagePoster.java
===================================================================
--- src/games/strategy/engine/pbem/PBEMMessagePoster.java	(revision 3313)
+++ src/games/strategy/engine/pbem/PBEMMessagePoster.java	(revision )
@@ -1,300 +1,202 @@
 package games.strategy.engine.pbem;
 
 import games.strategy.engine.data.GameData;
-import games.strategy.engine.framework.GameDataManager;
+import games.strategy.engine.data.PlayerID;
 import games.strategy.engine.history.IDelegateHistoryWriter;
 
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.Serializable;
 
-import javax.imageio.ImageIO;
-
+/**
+ * This class is responsible for posting turn summary and email at the end of each round in a PBEM game.
+ * A new instance is created at end of turn, based on the Email and a forum poster stored in the game data.
+ * The needs to be serialized since it is invoked through the IAbstractEndTurnDelegate which require all objects to be serializable
+ * although the PBEM games will always be local
+ * @author unascribed
+ * @author Klaus Groenbaek
+ *
+ */
 public class PBEMMessagePoster implements Serializable
 {
-	private static final String TURNSUMMARY_MSGR_PROP_NAME = "games.strategy.engine.framework.PBEMMessagePoster.TURNSUMMARY_MSGR";
-	private static final String SCREENSHOT_MSGR_PROP_NAME = "games.strategy.engine.framework.PBEMMessagePoster.SCREENSHOT_MSGR";
-	private static final String SAVEGAME_MSGR_PROP_NAME = "games.strategy.engine.framework.PBEMMessagePoster.SAVEGAME_MSGR";
-	private GameData m_gameData = null;
-	private IPBEMMessenger m_turnSummaryMessenger = null;
-	private IPBEMMessenger m_screenshotMessenger = null;
-	private IPBEMMessenger m_saveGameMessenger = null;
-	private String m_screenshotFilename = null;
-	private InputStream m_screenshotFileIn = null;
-	private String m_saveGameFilename = null;
-	private InputStream m_saveGameFileIn = null;
-	private String m_turnSummary = null;
-	private String m_screenshotRef = null;
-	private String m_saveGameRef = null;
-	private String m_turnSummaryRef = null;
+	//-----------------------------------------------------------------------
+	// class fields
+	//-----------------------------------------------------------------------
+	public static final String FORUM_POSTER_PROP_NAME = "games.strategy.engine.pbem.IForumPoster";
+	public static final String EMAIL_SENDER_PROP_NAME  = "games.strategy.engine.pbem.IEmailSender";
+	private static final long serialVersionUID = 2256265436928530566L;
-	
+
-	public PBEMMessagePoster()
-	{
-	}
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+	private IForumPoster m_forumPoster = null;
+	private IEmailSender m_emailSender;
+	private transient File m_saveGameFile = null;
+	private transient String m_turnSummary = null;
+	private transient String m_saveGameRef = null;
+	private transient String m_turnSummaryRef = null;
+	private transient String m_emailSendStatus;
+	private transient PlayerID m_currentPlayer;
+	private transient int m_roundNumber;
-	
+
-	public PBEMMessagePoster(final GameData gameData)
+	//-----------------------------------------------------------------------
+	// Constructors
+	//-----------------------------------------------------------------------
+
+	public PBEMMessagePoster(final GameData gameData, PlayerID currentPlayer, int roundNumber)
 	{
-		m_gameData = gameData;
-		setTurnSummaryMessenger((IPBEMMessenger) gameData.getProperties().get(TURNSUMMARY_MSGR_PROP_NAME));
-		setScreenshotMessenger((IPBEMMessenger) gameData.getProperties().get(SCREENSHOT_MSGR_PROP_NAME));
-		setSaveGameMessenger((IPBEMMessenger) gameData.getProperties().get(SAVEGAME_MSGR_PROP_NAME));
+		m_currentPlayer = currentPlayer;
+		m_roundNumber = roundNumber;
+		m_forumPoster = (IForumPoster) gameData.getProperties().get(FORUM_POSTER_PROP_NAME);
+		m_emailSender = (IEmailSender) gameData.getProperties().get(EMAIL_SENDER_PROP_NAME);
 	}
-	
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
 	public boolean hasMessengers()
 	{
-		return (m_turnSummaryMessenger != null || m_screenshotMessenger != null || m_saveGameMessenger != null);
+		return (m_forumPoster != null || m_emailSender != null);
 	}
 	
-	public void setTurnSummaryMessenger(final IPBEMMessenger msgr)
+	public void setForumPoster(final IForumPoster msgr)
 	{
-		m_turnSummaryMessenger = msgr;
+		m_forumPoster = msgr;
 	}
 	
-	public IPBEMTurnSummaryMessenger getTurnSummaryMessenger()
+	public IForumPoster getForumPoster()
 	{
-		final IPBEMMessenger msgr = m_turnSummaryMessenger;
-		if (msgr instanceof IPBEMTurnSummaryMessenger)
-			return (IPBEMTurnSummaryMessenger) msgr;
-		else
-			return null;
+		return m_forumPoster;
 	}
 	
-	public void setScreenshotMessenger(final IPBEMMessenger msgr)
-	{
-		m_screenshotMessenger = msgr;
-	}
-	
+
-	public IPBEMScreenshotMessenger getScreenshotMessenger()
-	{
-		final IPBEMMessenger msgr = m_screenshotMessenger;
-		if (msgr instanceof IPBEMScreenshotMessenger)
-			return (IPBEMScreenshotMessenger) msgr;
-		else
-			return null;
-	}
-	
-	public void setSaveGameMessenger(final IPBEMMessenger msgr)
-	{
-		m_saveGameMessenger = msgr;
-	}
-	
-	public IPBEMSaveGameMessenger getSaveGameMessenger()
-	{
-		final IPBEMMessenger msgr = m_saveGameMessenger;
-		if (msgr instanceof IPBEMSaveGameMessenger)
-			return (IPBEMSaveGameMessenger) msgr;
-		else
-			return null;
-	}
-	
 	public void setTurnSummary(final String turnSummary)
 	{
 		m_turnSummary = turnSummary;
 	}
-	
+
-	public void setScreenshot(final File screenshotFile) throws FileNotFoundException
-	{
-		if (screenshotFile != null)
-			setScreenshot(screenshotFile.getName(), ((new FileInputStream(screenshotFile))));
-		else
-			setScreenshot(null, null);
-	}
-	
-	public void setScreenshot(final String filename, final InputStream fileIn)
-	{
-		m_screenshotFilename = filename;
-		m_screenshotFileIn = fileIn;
-	}
-	
 	public void setSaveGame(final File saveGameFile) throws FileNotFoundException
 	{
-		if (saveGameFile != null)
-			setSaveGame(saveGameFile.getName(), ((new FileInputStream(saveGameFile))));
-		else
-			setSaveGame(null, null);
+		m_saveGameFile = saveGameFile;
 	}
-	
+
-	public void setSaveGame(final String filename, final InputStream fileIn)
-	{
-		m_saveGameFilename = filename;
-		m_saveGameFileIn = fileIn;
-	}
-	
 	public String getTurnSummaryRef()
 	{
 		return m_turnSummaryRef;
 	}
 	
-	public String getScreenshotRef()
-	{
-		return m_screenshotRef;
-	}
-	
 	public String getSaveGameRef()
 	{
 		return m_saveGameRef;
 	}
-	
+
-	private void setTestData()
+	/**
+	 * Post summary to form and/or email, and writes the action performed to the history writer
+	 * @param historyWriter the history writer (which has no effect since save game has already be generated...) // todo (kg)
+	 * @return true if all posts were successful
+	 */
+	public boolean post(final IDelegateHistoryWriter historyWriter)
 	{
-		final IPBEMScreenshotMessenger screenshotMsgr = getScreenshotMessenger();
-		final IPBEMSaveGameMessenger saveGameMsgr = getSaveGameMessenger();
-		final IPBEMTurnSummaryMessenger turnSummaryMsgr = getTurnSummaryMessenger();
-		// set screenshot
-		if (screenshotMsgr != null)
+		boolean forumSuccess = true;
+
+		final StringBuilder saveGameSb = new StringBuilder().append("triplea_");
+		if (m_forumPoster != null)
 		{
-			final BufferedImage testImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
-			final Graphics g = testImage.createGraphics();
-			g.setFont(new Font("Ariel", Font.BOLD, 15));
-			g.setColor(Color.WHITE);
-			g.drawString("Test Post", 15, 15);
-			try
-			{
-				final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
-				if (ImageIO.write(testImage, "jpg", byteOut))
-				{
-					final ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
-					setScreenshot("test.jpg", byteIn);
+			saveGameSb.append(m_forumPoster.getForumId()).append("_");
-				}
+		}
-			} catch (final IOException ioe)
+		saveGameSb.append(m_currentPlayer.getName().substring(0, 1)).append(m_roundNumber).append(".tsvg");
+
+
+		String saveGameName = saveGameSb.toString();
+		if (m_forumPoster != null)
-			{
+		{
-				ioe.printStackTrace();
-			}
-		}
-		// set save game
-		if (saveGameMsgr != null)
+			if (m_forumPoster.getIncludeSaveGame())
-		{
+			{
-			File saveGameFile = null;
+				m_forumPoster.addSaveGame(m_saveGameFile,saveGameName);
+			}
 			try
 			{
-				saveGameFile = File.createTempFile("triplea", ".tsvg");
-				(new GameDataManager()).saveGame(saveGameFile, m_gameData);
-				setSaveGame(saveGameFile);
+				forumSuccess = m_forumPoster.postTurnSummary(m_turnSummary);
+				m_turnSummaryRef = m_forumPoster.getTurnSummaryRef();
+				if (m_turnSummaryRef != null && historyWriter != null)
+				{
+					historyWriter.addChildToEvent("Turn Summary: " + m_turnSummaryRef, null);
+				}
 			} catch (final Exception e)
 			{
 				e.printStackTrace();
 			}
 		}
-		// set turn summary
-		if (turnSummaryMsgr != null)
-			setTurnSummary("TripleA test for turn summary auto-posting.");
-	}
-	
+
-	public boolean postTestData()
+		boolean emailSuccess = true;
+		if (m_emailSender != null)
-	{
+		{
-		setTestData();
-		return post(null);
-	}
-	
-	public boolean post(final IDelegateHistoryWriter historyWriter)
-	{
-		boolean retval = true;
-		final IPBEMScreenshotMessenger screenshotMsgr = getScreenshotMessenger();
-		final IPBEMSaveGameMessenger saveGameMsgr = getSaveGameMessenger();
-		final IPBEMTurnSummaryMessenger turnSummaryMsgr = getTurnSummaryMessenger();
+			StringBuilder subjectPostFix = new StringBuilder(m_currentPlayer.getName());
+			subjectPostFix.append(" - ").append("round ").append(m_roundNumber);
-		try
-		{
+			try
+			{
-			if (screenshotMsgr != null)
-				if (screenshotMsgr.postScreenshot(m_screenshotFilename, m_screenshotFileIn))
-					m_screenshotRef = screenshotMsgr.getScreenshotRef();
-				else
-					retval = false;
-		} catch (final Exception e)
+				m_emailSender.sendEmail(subjectPostFix.toString(), convertToHtml(m_turnSummary), m_saveGameFile, saveGameName);
+				m_emailSendStatus = "Success, sent to " + m_emailSender.getToAddress();
+
+			} catch (IOException e)
-		{
+			{
+				emailSuccess = false;
+				m_emailSendStatus = "Failed! Error " + e.getMessage();
-			e.printStackTrace();
-		}
+				e.printStackTrace();
+			}
-		try
+		}
+
+		if (historyWriter != null)
 		{
-			if (saveGameMsgr != null)
-				if (saveGameMsgr.postSaveGame(m_saveGameFilename, m_saveGameFileIn))
-					m_saveGameRef = saveGameMsgr.getSaveGameRef();
-				else
-					retval = false;
-		} catch (final Exception e)
+			StringBuilder sb = new StringBuilder("Post Turn Summary");
+			if (m_forumPoster != null)
-		{
+			{
-			e.printStackTrace();
+				sb.append(" to ").append(m_forumPoster.getDisplayName()).append(" success = ").append(String.valueOf(forumSuccess));
-		}
+			}
-		// screenshot/save game refs might be null here, if same IPBEMMessenger object is used for turnSummaryMsgr
-		// pass them as-is, but re-fetch them below
-		try
+			if (m_emailSender != null)
-		{
+			{
-			if (turnSummaryMsgr != null && !turnSummaryMsgr.postTurnSummary(m_turnSummary, m_screenshotRef, m_saveGameRef))
-				retval = false;
-		} catch (final Exception e)
+				if (m_forumPoster != null)
-		{
+				{
-			e.printStackTrace();
-		}
-		// Re-fetch all refs and write history last.
-		// refs might not be set until all posts are done,
-		// and won't be set yet if there were posting errors
-		if (historyWriter != null)
-			historyWriter.startEvent("Post Turn Summary");
-		if (screenshotMsgr != null)
+					sb.append(" and to ");
+				} else
-		{
+				{
-			m_screenshotRef = screenshotMsgr.getScreenshotRef();
-			if (m_screenshotRef != null && historyWriter != null)
-				historyWriter.addChildToEvent("Screenshot: " + m_screenshotRef, null);
+					sb.append(" to ");
-		}
+				}
-		if (saveGameMsgr != null)
-		{
-			m_saveGameRef = saveGameMsgr.getSaveGameRef();
-			if (m_saveGameRef != null && historyWriter != null)
-				historyWriter.addChildToEvent("Save Game: " + m_saveGameRef, null);
+				sb.append(m_emailSender.getToAddress()).append(" success = ").append(String.valueOf(emailSuccess));
-		}
+			}
-		if (turnSummaryMsgr != null)
-		{
-			m_turnSummaryRef = turnSummaryMsgr.getTurnSummaryRef();
-			if (m_turnSummaryRef != null && historyWriter != null)
-				historyWriter.addChildToEvent("Turn Summary: " + m_turnSummaryRef, null);
+			historyWriter.startEvent(sb.toString());
 		}
-		// finally, close input streams
-		try
+
+
+		return forumSuccess && emailSuccess;
+	}
+
+	/**
+	 * Converts text to html, by transforming \n to <br/>
+	 * @param string the string to transform
+	 * @return the transformed string
+	 */
+	private String convertToHtml(String string)
-		{
+	{
-			if (m_screenshotFileIn != null)
-				m_screenshotFileIn.close();
-		} catch (final Exception e)
-		{
-			e.printStackTrace();
+		return string.replaceAll("\n", "<br/>");
-		}
+	}
-		try
+
+	/**
+	 * Get the configured email sender
+	 * @return return an email sender or null
+	 */
+	public IEmailSender getEmailSender()
-		{
+	{
-			if (m_saveGameFileIn != null)
-				m_saveGameFileIn.close();
-		} catch (final Exception e)
-		{
-			e.printStackTrace();
+		return m_emailSender;
-		}
+	}
-		return retval;
-	}
-	
+
-	public void storeMessengers(final GameData gameData)
+	/**
+	 * Return the status string from sending the email.
+	 * @return a success of failure string, or null if no email sender was configured
+	 */
+	public String getEmailSendStatus()
 	{
-		// If set to the NullPBEMMessenger object, just save null
-		// to increase portability.
-		Object saveObj;
-		if (m_turnSummaryMessenger instanceof NullPBEMMessenger)
-			saveObj = null;
-		else
-			saveObj = m_turnSummaryMessenger;
-		gameData.getProperties().set(TURNSUMMARY_MSGR_PROP_NAME, saveObj);
-		if (m_screenshotMessenger instanceof NullPBEMMessenger)
-			saveObj = null;
-		else
-			saveObj = m_screenshotMessenger;
-		gameData.getProperties().set(SCREENSHOT_MSGR_PROP_NAME, saveObj);
-		if (m_saveGameMessenger instanceof NullPBEMMessenger)
-			saveObj = null;
-		else
-			saveObj = m_saveGameMessenger;
-		gameData.getProperties().set(SAVEGAME_MSGR_PROP_NAME, saveObj);
+		return m_emailSendStatus;
 	}
 }
Index: src/games/strategy/engine/framework/startup/ui/editors/validators/EmailValidator.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/validators/EmailValidator.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/validators/EmailValidator.java	(revision )
@@ -0,0 +1,32 @@
+package games.strategy.engine.framework.startup.ui.editors.validators;
+
+import games.strategy.util.Util;
+
+/**
+ * A validator which validates that a text string is an email
+ * @author Klaus Groenbaek
+ */
+public class EmailValidator implements IValidator
+{
+
+	private boolean m_mayBeEmpty;
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	public EmailValidator(boolean mayBeEmpty)
+	{
+		m_mayBeEmpty = mayBeEmpty;
+	}
+
+	public boolean isValid(String text)
+	{
+		if (text.isEmpty() ) {
+			return m_mayBeEmpty;
+		}
+		return Util.isMailValid(text);
+
+
+	}
+}
Index: src/games/puzzle/slidingtiles/NPuzzle.java
===================================================================
--- src/games/puzzle/slidingtiles/NPuzzle.java	(revision 3313)
+++ src/games/puzzle/slidingtiles/NPuzzle.java	(revision )
@@ -29,17 +29,14 @@
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.AllYouCanUploadDotComPBEMMessenger;
-import games.strategy.engine.pbem.IPBEMMessenger;
 
+import javax.swing.*;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
-import javax.swing.SwingUtilities;
-
 /**
  * Main class responsible for an n-puzzle game.
  * 
@@ -55,9 +52,6 @@
 	private static final String RANDOM_COMPUTER_PLAYER_TYPE = "Random AI";
 	private static final String DFS_COMPUTER_PLAYER_TYPE = "Depth First Search AI";
 	
-	/**
-	 * @see IGameLoader.createPlayers(playerNames)
-	 */
 	public Set<IGamePlayer> createPlayers(final Map<String, String> playerNames)
 	{
 		final Set<IGamePlayer> players = new HashSet<IGamePlayer>();
@@ -155,12 +149,7 @@
 				((NPuzzlePlayer) player).setFrame(frame);
 		}
 	}
-	
+
-	public IPBEMMessenger[] getPBEMMessengers()
-	{
-		return new IPBEMMessenger[] { new AllYouCanUploadDotComPBEMMessenger() };
-	}
-	
 	/**
 	 * @see games.strategy.engine.framework.IGameLoader#getDisplayType()
 	 */
Index: src/games/strategy/engine/random/PropertiesDiceRoller.java
===================================================================
--- src/games/strategy/engine/random/PropertiesDiceRoller.java	(revision 3313)
+++ src/games/strategy/engine/random/PropertiesDiceRoller.java	(revision )
@@ -14,16 +14,20 @@
 package games.strategy.engine.random;
 
 import games.strategy.engine.EngineVersion;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Properties;
-import java.util.StringTokenizer;
-
+import games.strategy.engine.framework.GameRunner;
+import games.strategy.engine.framework.startup.ui.editors.DiceServerEditor;
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.NameValuePair;
 import org.apache.commons.httpclient.methods.PostMethod;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
 /**
  * A pbem dice roller that reads its configuration from a properties file
  * 
@@ -32,24 +36,107 @@
  */
 public class PropertiesDiceRoller implements IRemoteDiceServer
 {
+	//-----------------------------------------------------------------------
+	// class fields
+	//-----------------------------------------------------------------------
+	private static final long serialVersionUID = 6481409417543119539L;
+
+
+	//-----------------------------------------------------------------------
+	// class methods
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Loads the property dice rollers from the properties file
+	 * @return the collection of available dice rollers
+	 */
+	public static Collection<PropertiesDiceRoller> loadFromFile() {
+	    List<PropertiesDiceRoller> rollers = new ArrayList<PropertiesDiceRoller>();
+		final File f = new File(GameRunner.getRootFolder(), "dice_servers");
+		if (!f.exists())
+		{
+			throw new IllegalStateException("No dice server folder:" + f);
+		}
+		final java.util.List<Properties> propFiles = new ArrayList<Properties>();
+		final File[] files = f.listFiles();
+		for (final File file : files)
+		{
+			if (!file.isDirectory() && file.getName().endsWith(".properties"))
+			{
+				try
+				{
+					final Properties props = new Properties();
+					final FileInputStream fin = new FileInputStream(file);
+					try
+					{
+						props.load(fin);
+						propFiles.add(props);
+					} finally
+					{
+						fin.close();
+					}
+				} catch (final IOException e)
+				{
+					System.out.println("error reading file:" + file);
+					e.printStackTrace();
+				}
+			}
+		}
+		Collections.sort(propFiles, new Comparator<Properties>()
+		{
+			public int compare(final Properties o1, final Properties o2)
+			{
+				final int n1 = Integer.parseInt(o1.getProperty("order"));
+				final int n2 = Integer.parseInt(o2.getProperty("order"));
+				return n1 - n2;
+			}
+		});
+
+		for (final Properties prop : propFiles)
+		{
+			rollers.add(new PropertiesDiceRoller(prop));
+		}
+
+		return rollers;
+	}
+
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
 	private final Properties m_props;
+	private String m_toAddress;
+	private String m_ccAddress;
+	private String m_gameId;
-	
+
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
 	public PropertiesDiceRoller(final Properties props)
 	{
 		m_props = props;
 	}
-	
+
-	public String getName()
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	public String getDisplayName()
 	{
 		return m_props.getProperty("name");
 	}
-	
+
-	@Override
-	public String toString()
+	public EditorPanel getEditor()
 	{
-		return getName();
+		return new DiceServerEditor(this);
 	}
-	
+
+	public boolean sameType(IBean other)
+	{
+		return other instanceof PropertiesDiceRoller && getDisplayName().equals(other.getDisplayName());
+	}
+
+
 	public boolean sendsEmail()
 	{
 		final String property = m_props.getProperty("send.email");
@@ -60,17 +147,17 @@
 		return Boolean.valueOf(property);
 	}
 	
-	public String postRequest(final String player1, final String player2, final int max, final int numDice, final String text, String gameID, final String gameUUID) throws IOException
+	public String postRequest(final int max, final int numDice, final String subjectMessage, String gameID, final String gameUUID) throws IOException
 	{
 		if (gameID.trim().length() == 0)
 			gameID = "TripleA";
-		String message = gameID + ":" + text;
+		String message = gameID + ":" + subjectMessage;
 		final int maxLength = Integer.valueOf(m_props.getProperty("message.maxlength"));
 		if (message.length() > maxLength)
 			message = message.substring(0, maxLength - 1);
 		final PostMethod post = new PostMethod(m_props.getProperty("path"));
 		final NameValuePair[] data = { new NameValuePair("numdice", "" + numDice), new NameValuePair("numsides", "" + max), new NameValuePair("modroll", "No"), new NameValuePair("numroll", "" + 1),
-					new NameValuePair("subject", message), new NameValuePair("roller", player1), new NameValuePair("gm", player2), new NameValuePair("send", "true"), };
+					new NameValuePair("subject", message), new NameValuePair("roller", getToAddress()), new NameValuePair("gm", getCcAddress()), new NameValuePair("send", "true"), };
 		post.setRequestHeader("User-Agent", "triplea/" + EngineVersion.VERSION);
 		// this is to allow a dice server to allow the user to request the emails for the game
 		// rather than sending out email for each roll
@@ -95,9 +182,9 @@
 		}
 	}
 	
-	public String getWebText()
+	public String getInfoText()
 	{
-		return m_props.getProperty("web.text");
+		return m_props.getProperty("infotext");
 	}
 	
 	/**
@@ -162,4 +249,40 @@
 		}
 		return rVal;
 	}
+
+	public String getToAddress()
+	{
+		return m_toAddress;
-}
+	}
+
+	public void setToAddress(String toAddress)
+	{
+		m_toAddress = toAddress;
+	}
+
+	public String getCcAddress()
+	{
+		return m_ccAddress;
+	}
+
+	public void setCcAddress(String ccAddress)
+	{
+		m_ccAddress = ccAddress;
+	}
+
+	public boolean supportsGameId()
+	{
+		String gameid = m_props.getProperty("gameid");
+		return "true".equals(gameid);
+	}
+
+	public void setGameId(String gameId)
+	{
+		 m_gameId = gameId;
+	}
+
+	public String getGameId()
+	{
+		return m_gameId;
+	}
+}
Index: dice_servers/tripleawarclub.properties
===================================================================
--- dice_servers/tripleawarclub.properties	(revision 1612)
+++ dice_servers/tripleawarclub.properties	(revision )
@@ -15,4 +15,6 @@
 #if error start and and are found in the returned email, 
 # we do not report dice, but instead show the error message.
 error.start=fatal error:
-error.end=!
\ No newline at end of file
+error.end=!
+infotext=Enter your your email address in the To field, and you opponents in in the CC field.\nEmail addresses must be registered at http://dice.tripleawarclub.org\nIf this is a league match you must enter a Game ID
+gameid=true
\ No newline at end of file
Index: dice_servers/tdice-web.properties
===================================================================
--- dice_servers/tdice-web.properties	(revision 2036)
+++ dice_servers/tdice-web.properties	(revision )
@@ -17,4 +17,5 @@
 error.start=<error>
 error.end=</error>
 send.email=false
-web.text=Validate dice at http://tdice.appspot.com/list \n\nUse the Game UUID supplied through the View->Game UUID menu.\n\nDice are stored for up to 60 days.
\ No newline at end of file
+infotext=Validate dice at http://tdice.appspot.com/list \nUse the Game UUID supplied through the View->Game UUID menu.\nDice are stored for up to 60 days.
+gameid=false
\ No newline at end of file
Index: build.xml
===================================================================
--- build.xml	(revision 3078)
+++ build.xml	(revision )
@@ -7,6 +7,8 @@
 
 	<!-- this path does not have junit on it -->
 	<path id="full.path">
+        <pathelement path="lib\mailapi.jar"/>
+        <pathelement path="lib\smtp.jar"/>
 		<pathelement path="lib\standalone-compiler.jar" />	
 		<pathelement path="lib\AppleJavaExtensions.jar" />
 		<pathelement path="lib\commons-httpclient-3.0.1.jar" />
Index: src/games/strategy/engine/xml/TestGameLoader.java
===================================================================
--- src/games/strategy/engine/xml/TestGameLoader.java	(revision 3313)
+++ src/games/strategy/engine/xml/TestGameLoader.java	(revision )
@@ -12,7 +12,6 @@
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.IPBEMMessenger;
 
 import java.util.Map;
 import java.util.Set;
@@ -48,14 +47,7 @@
 	{
 	}
 	
-	/**
-	 * Get PBEM messengers for Turn Summary notification
-	 */
-	public IPBEMMessenger[] getPBEMMessengers()
-	{
-		return new IPBEMMessenger[0];
-	}
-	
+
 	/**
 	 * Create the players. Given a map of playerName -> type,
 	 * where type is one of the Strings returned by a get*PlayerType() method.
Index: src/games/strategy/engine/framework/startup/ui/editors/ForumPosterEditor.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/ForumPosterEditor.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/ForumPosterEditor.java	(revision )
@@ -0,0 +1,234 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import games.strategy.engine.framework.startup.ui.MainFrame;
+import games.strategy.engine.pbem.IForumPoster;
+import games.strategy.engine.pbem.NullForumPoster;
+import games.strategy.ui.ProgressWindow;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * A class for selecting which Forum poster to use
+ *
+ * @author Klaus Groenbaek
+ */
+public class ForumPosterEditor extends EditorPanel
+{
+
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+	private final JButton m_viewPosts = new JButton("View Forum");
+	private final JButton m_testForum = new JButton("Test Post");
+
+	private JLabel m_loginLabel = new JLabel("Login:");
+	private JLabel m_passwordLabel = new JLabel("Password:");
+	private final JTextField m_login = new JTextField();
+	private final JTextField m_password = new JPasswordField();
+
+	private JTextField m_forumIdField = new JTextField();
+	private JLabel m_forumIdLabel = new JLabel("Forum Id:");
+
+	private JCheckBox m_includeSaveGame = new JCheckBox("Attach save game to summary");
+	private IForumPoster m_bean;
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+
+	public ForumPosterEditor(IForumPoster bean)
+	{
+		m_bean = bean;
+
+		int bottomSpace = 1;
+		int labelSpace = 2;
+		int row = 0;
+		if (m_bean.getCanViewPosted())
+		{
+			add(m_forumIdLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+			add(m_forumIdField, new GridBagConstraints(1, row, 1, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+			m_forumIdField.setText(m_bean.getForumId());
+			add(m_viewPosts, new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 2, bottomSpace, 0), 0, 0));
+			row++;
+		}
+
+		add(m_loginLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+		add(m_login, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+		m_login.setText(m_bean.getUsername());
+		row++;
+
+		add(m_passwordLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+		add(m_password, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+		m_password.setText(m_bean.getPassword());
+		row++;
+
+		if (m_bean.supportsSaveGame())
+		{
+			add(m_includeSaveGame, new GridBagConstraints(0, row, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+			m_includeSaveGame.setSelected(m_bean.getIncludeSaveGame());
+			add(m_testForum, new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+		} else
+		{
+			add(m_testForum, new GridBagConstraints(3, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+		}
+
+		setupListeners();
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	/**
+	 * Configures the listeners for the gui components
+	 */
+	private void setupListeners()
+	{
+
+		m_viewPosts.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(ActionEvent e)
+			{
+				m_bean.viewPosted();
+			}
+		});
+
+		m_testForum.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(final ActionEvent e)
+			{
+				testForum();
+			}
+		});
+
+
+		// add a document listener which will validate input when the content of any input field is changed
+		DocumentListener docListener = new ValidateInputOnChangeListener();
+		m_login.getDocument().addDocumentListener(docListener);
+		m_password.getDocument().addDocumentListener(docListener);
+		m_forumIdField.getDocument().addDocumentListener(docListener);
+	}
+
+	/**
+	 * Tests the Forum poster
+	 */
+	void testForum()
+	{
+		final ProgressWindow progressWindow = new ProgressWindow(MainFrame.getInstance(), "Testing Forum Post...");
+		progressWindow.setVisible(true);
+
+		Runnable runnable = new Runnable()
+		{
+			public void run()
+			{
+				m_bean.postTurnSummary("Test summary");
+				progressWindow.setVisible(false);
+
+				// now that we have a result, marshall it back unto the swing thread
+				SwingUtilities.invokeLater(new Runnable()
+				{
+					public void run()
+					{
+						try
+						{
+							JOptionPane.showMessageDialog(MainFrame.getInstance(), m_bean.getTurnSummaryRef(), "Test Turn Summary Post", JOptionPane.INFORMATION_MESSAGE);
+						} catch (HeadlessException e)
+						{
+							// should never happen in a GUI app
+						}
+					}
+				});
+			}
+		};
+		// start a background thread
+		Thread t = new Thread(runnable);
+		t.start();
+	}
+
+
+
+	/**
+	 * Fires a property change so anyone listening to this editor will be notified
+	 */
+	private void notifyListeners()
+	{
+		firePropertyChange(EDITOR_CHANGE, null, null);
+	}
+
+
+
+	public boolean isInputValid()
+	{
+		if (m_bean instanceof NullForumPoster)
+		{
+			return true;
+		}
+		boolean loginValid = validateTextField(m_login, m_loginLabel);
+		boolean passwordValid = validateTextField(m_password, m_passwordLabel);
+
+		boolean idValid = true;
+		if (m_bean.getCanViewPosted())
+		{
+			idValid = validateTextField(m_forumIdField, m_forumIdLabel);
+			m_viewPosts.setEnabled(idValid);
+		} else
+		{
+			m_forumIdLabel.setForeground(m_labelColor);
+			m_viewPosts.setEnabled(false);
+		}
+
+
+		boolean allValid = loginValid && passwordValid && idValid;
+		m_testForum.setEnabled(allValid);
+		return allValid;
+	}
+
+	@Override
+	public IBean getBean()
+	{
+		return getForumPoster();
+	}
+	/**
+	 * Get the Forum poster edited  by the editor
+	 *
+	 * @return the poster
+	 */
+	public IForumPoster getForumPoster()
+	{
+		m_bean.setForumId(m_forumIdField.getText());
+		m_bean.setUsername(m_login.getText());
+		m_bean.setPassword(m_password.getText());
+		m_bean.setIncludeSaveGame(m_includeSaveGame.isSelected());
+		return m_bean;
+	}
+
+	//-----------------------------------------------------------------------
+	// inner classes
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Document listener which calls validateAndEnableInput in response to any document change
+	 */
+	private class ValidateInputOnChangeListener implements DocumentListener
+	{
+		public void changedUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void insertUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void removeUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+	}
+}
Index: src/games/strategy/engine/random/InternalDiceServer.java
===================================================================
--- src/games/strategy/engine/random/InternalDiceServer.java	(revision )
+++ src/games/strategy/engine/random/InternalDiceServer.java	(revision )
@@ -0,0 +1,135 @@
+package games.strategy.engine.random;
+
+import games.strategy.engine.framework.startup.ui.editors.DiceServerEditor;
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * This is not actually a dice server, it just uses the normal TripleA PlainRandomSource for dice roll
+ * This way your dice rolls are not registered anywhere, and you do not rely on any external web based service rolling
+ * the dice.
+ *
+ * Because DiceServers must be serializable read resolve must be implemented
+ * @author Klaus Groenbaek
+ */
+public class InternalDiceServer implements IRemoteDiceServer
+{
+	private static final long serialVersionUID = -8369097763085658445L;
+	//-----------------------------------------------------------------------
+	// instance fields 
+	//-----------------------------------------------------------------------
+	private transient IRandomSource _randomSource;
+
+	//-----------------------------------------------------------------------
+	// constructor
+	//-----------------------------------------------------------------------
+
+	public InternalDiceServer()
+	{
+		_randomSource = new PlainRandomSource();
+	}
+
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+
+	public EditorPanel getEditor()
+	{
+		return new DiceServerEditor(this);
+	}
+
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == InternalDiceServer.class;
+	}
+
+	public String postRequest(int max, int numDice, String subjectMessage, String gameID, String gameUUID) throws IOException
+	{
+		// the interface is rather stupid, you have to return a string here, which is then passed back in getDice()
+		int[] ints = _randomSource.getRandom(max, numDice, "Internal Dice Server");
+		StringBuilder sb = new StringBuilder();
+		for (int i : ints)
+		{
+		 	sb.append(i).append(",");
+		}
+
+		String intArrayString = sb.substring(0, sb.length()-1);
+		return intArrayString;
+	}
+
+	public int[] getDice(String string, int count) throws IOException, InvocationTargetException
+	{
+		String[] strArray = string.split(",");
+		int[] intArray = new int[strArray.length];
+		for (int i=0; i< strArray.length ;i++) {
+			intArray[i] = Integer.parseInt(strArray[i]);
+		}
+		return intArray;
+	}
+
+	public String getDisplayName()
+	{
+		return "Internal Dice Roller";
+	}
+
+	public String getToAddress()
+	{
+		return null;
+	}
+
+	public void setToAddress(String toAddress)
+	{
+
+	}
+
+	public String getCcAddress()
+	{
+		return null;
+	}
+
+	public void setCcAddress(String ccAddress)
+	{
+
+	}
+
+	public String getInfoText()
+	{
+		return "Uses the build in TripleA dice roller.\nDice are not logged, and no internet access is required.\nIt is technically possible (for a hacker) to modify the dice rolls.";
+	}
+
+	public boolean sendsEmail()
+	{
+		return false;
+	}
+
+	/**
+	 * Dice servers has to be serializable, so we need to provide custom serialization since
+	 * PlainRandomSource is not serializable
+	 * @return a new InternalDiceServer
+	 * @throws ObjectStreamException should never occur (unless runtime exceptions is thrown from constructor)
+	 */
+	public Object readResolve() throws ObjectStreamException {
+	 	return new InternalDiceServer();
+	}
+
+	public boolean supportsGameId()
+	{
+		return false;
+	}
+
+	public void setGameId(String gameId)
+	{
+
+	}
+
+	public String getGameId()
+	{
+		return null;
+	}
+}
Index: src/games/strategy/engine/pbem/NullEmailSender.java
===================================================================
--- src/games/strategy/engine/pbem/NullEmailSender.java	(revision )
+++ src/games/strategy/engine/pbem/NullEmailSender.java	(revision )
@@ -0,0 +1,77 @@
+package games.strategy.engine.pbem;
+
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A dummy Email sender, to use when Email sending is disabled
+ * @author Klaus Groenbaek
+ */
+public class NullEmailSender implements IEmailSender
+{
+	//-----------------------------------------------------------------------
+	// class fields
+	//-----------------------------------------------------------------------
+	private static final long serialVersionUID = 9138507282128548506L;
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	public String getDisplayName()
+	{
+		return "disabled";
+	}
+
+	public void sendEmail(String subject, String htmlMessage, File saveGame, String fileName) throws IOException
+	{
+	}
+
+	public String getToAddress()
+	{
+		return null;
+	}
+
+	public void clearSensitiveInfo()
+	{
+
+	}
+
+	public IEmailSender doClone()
+	{
+		return new NullEmailSender();
+	}
+
+	public EditorPanel getEditor()
+	{
+		return null;
+	}
+
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == NullEmailSender.class;
+	}
+
+	public String getUserName()
+	{
+		return null;
+	}
+
+	public String getPassword()
+	{
+		return null;
+	}
+
+	public void setUserName(String userName)
+	{
+
+	}
+
+	public void setPassword(String password)
+	{
+
+	}
+}
Index: dice_servers/tdice-email.properties
===================================================================
--- dice_servers/tdice-email.properties	(revision 2036)
+++ dice_servers/tdice-email.properties	(revision )
@@ -15,4 +15,6 @@
 #if error start and and are found in the returned email, 
 # we do not report dice, but instead show the error message.
 error.start=<error>
-error.end=</error>
\ No newline at end of file
+error.end=</error>
+infotext=You can enter up to 5 addresses in the To or CC fields, separating each address by a space.\nYou must enter an address in the To: field.
+gameid=true
\ No newline at end of file
Index: src/games/strategy/triplea/ui/EndTurnPanel.java
===================================================================
--- src/games/strategy/triplea/ui/EndTurnPanel.java	(revision 3313)
+++ src/games/strategy/triplea/ui/EndTurnPanel.java	(revision )
@@ -23,25 +23,18 @@
 import games.strategy.engine.gamePlayer.IPlayerBridge;
 import games.strategy.engine.history.HistoryNode;
 import games.strategy.engine.history.Round;
-import games.strategy.engine.pbem.IPBEMMessenger;
+import games.strategy.engine.pbem.IEmailSender;
+import games.strategy.engine.pbem.IForumPoster;
 import games.strategy.engine.pbem.PBEMMessagePoster;
 import games.strategy.triplea.delegate.remote.IAbstractEndTurnDelegate;
 import games.strategy.triplea.ui.history.HistoryLog;
 import games.strategy.ui.ProgressWindow;
 
+import javax.swing.*;
 import java.awt.event.ActionEvent;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 
-import javax.swing.AbstractAction;
-import javax.swing.Action;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
 /**
  * 
  * @author Tony Clayton
@@ -81,15 +74,25 @@
 			public void actionPerformed(final ActionEvent event)
 			{
 				String message = "";
-				final IPBEMMessenger screenshotMsgr = m_poster.getScreenshotMessenger();
-				final IPBEMMessenger saveGameMsgr = m_poster.getSaveGameMessenger();
-				final IPBEMMessenger turnSummaryMsgr = m_poster.getTurnSummaryMessenger();
-				if (screenshotMsgr != null)
-					message = (new StringBuilder()).append(message).append("Post screenshot to ").append(screenshotMsgr.getName()).append("?\n").toString();
-				if (saveGameMsgr != null)
-					message = (new StringBuilder()).append(message).append("Post save game file to ").append(saveGameMsgr.getName()).append("?\n").toString();
+				final IForumPoster turnSummaryMsgr = m_poster.getForumPoster();
+
+				StringBuilder sb = new StringBuilder();
 				if (turnSummaryMsgr != null)
-					message = (new StringBuilder()).append(message).append("Post turn summary to ").append(turnSummaryMsgr.getName()).append("?\n").toString();
+				{
+					sb.append(message).append("Post turn summary ");
+					if (turnSummaryMsgr.getIncludeSaveGame())
+					{
+					    sb.append(" and save game ");
+					}
+					sb.append("to ").append(turnSummaryMsgr.getDisplayName()).append("?\n");
+				}
+
+				IEmailSender emailSender = m_poster.getEmailSender();
+				if (emailSender != null) {
+					sb.append("Send email to ").append(emailSender.getToAddress()).append("?\n");
+				}
+
+				message = sb.toString();
 				final int choice = JOptionPane.showConfirmDialog(getTopLevelAncestor(), message, "Post Turn Summary?", 2, -1, null);
 				if (choice != 0)
 				{
@@ -98,70 +101,37 @@
 				else
 				{
 					m_postButton.setEnabled(false);
+					final ProgressWindow progressWindow = new ProgressWindow(m_frame, "Posting Turn Summary...");
+					progressWindow.setVisible(true);
 					final Runnable t = new Runnable()
 					{
 						public void run()
 						{
 							boolean postOk = true;
-							final ProgressWindow progressWindow = new ProgressWindow(m_frame, "Posting Turn Summary...");
-							progressWindow.setVisible(true);
+
 							final IAbstractEndTurnDelegate delegate = (IAbstractEndTurnDelegate) m_bridge.getRemote();
 							delegate.setHasPostedTurnSummary(true);
-							File screenshotFile = null;
 							File saveGameFile = null;
-							if (screenshotMsgr != null)
-							{
+
-								try
-								{
+							try
+							{
-									screenshotFile = File.createTempFile("triplea", ".png");
-									if (screenshotFile != null && m_frame.saveScreenshot(getData().getHistory().getLastNode(), screenshotFile))
-										m_poster.setScreenshot(screenshotFile);
-								} catch (final Exception e)
-								{
-									postOk = false;
-									e.printStackTrace();
-								}
-							}
-							if (saveGameMsgr != null)
-							{
-								try
-								{
-									saveGameFile = File.createTempFile("triplea", ".tsvg");
-									if (saveGameFile != null)
-									{
+								saveGameFile = File.createTempFile("triplea", ".tsvg");
+								if (saveGameFile != null)
+								{
-										int round = 0;
-										final Object pathFromRoot[] = getData().getHistory().getLastNode().getPath();
-										final Object arr$[] = pathFromRoot;
-										final int len$ = arr$.length;
-										int i$ = 0;
-										do
-										{
-											if (i$ >= len$)
-												break;
-											final Object pathNode = arr$[i$];
-											final HistoryNode curNode = (HistoryNode) pathNode;
-											if (curNode instanceof Round)
-											{
-												round = ((Round) curNode).getRoundNo();
-												break;
-											}
-											i$++;
-										} while (true);
-										m_frame.getGame().saveGame(saveGameFile);
+									m_frame.getGame().saveGame(saveGameFile);
-										final StringBuilder saveGameSb = (new StringBuilder()).append("triplea_").append(saveGameMsgr.getGameId()).append("_")
-													.append(getCurrentPlayer().getName().substring(0, 1)).append(round).append(".tsvg");
-										m_poster.setSaveGame(saveGameSb.toString(), new FileInputStream(saveGameFile));
+									m_poster.setSaveGame(saveGameFile);
-									}
-								} catch (final Exception e)
-								{
-									postOk = false;
-									e.printStackTrace();
-								}
+								}
+							} catch (final Exception e)
+							{
+								postOk = false;
+								e.printStackTrace();
+							}
-							}
-							if (turnSummaryMsgr != null)
+
-								m_poster.setTurnSummary(m_historyLog.toString());
+							m_poster.setTurnSummary(m_historyLog.toString());
+
 							try
 							{
+								// forward the poster to the delegate which invokes post() on the poster
 								if (!delegate.postTurnSummary(m_poster))
 									postOk = false;
 							} catch (final Exception e)
@@ -169,23 +139,30 @@
 								postOk = false;
 								e.printStackTrace();
 							}
-							final String screenshotRef = m_poster.getScreenshotRef();
+
+							StringBuilder sb = new StringBuilder();
+							if (m_poster.getForumPoster() != null)
+							{
-							final String saveGameRef = m_poster.getSaveGameRef();
-							final String turnSummaryRef = m_poster.getTurnSummaryRef();
+								final String saveGameRef = m_poster.getSaveGameRef();
+								final String turnSummaryRef = m_poster.getTurnSummaryRef();
-							String message = "";
-							if (screenshotRef != null)
-								message = (new StringBuilder()).append(message).append("\nScreenshot: ").append(screenshotRef).toString();
+
-							if (saveGameRef != null)
+								if (saveGameRef != null)
-								message = (new StringBuilder()).append(message).append("\nSave Game : ").append(saveGameRef).toString();
+									sb.append("\nSave Game : ").append(saveGameRef);
-							if (turnSummaryRef != null)
+								if (turnSummaryRef != null)
-								message = (new StringBuilder()).append(message).append("\nSummary Text: ").append(turnSummaryRef).toString();
-							m_historyLog.getWriter().println(message);
-							if (m_historyLog.isVisible())
+									sb.append("\nSummary Text: ").append(turnSummaryRef);
+							}
+							if (m_poster.getEmailSender() != null)
+							{
+								sb.append("\nEmails: ").append(m_poster.getEmailSendStatus());
+							}
+
+
+							m_historyLog.getWriter().println(sb.toString());
+
+							if (m_historyLog.isVisible()) //todo(kg) I think this is a brain fart, unless is is a workaround for some bug
 								m_historyLog.setVisible(true);
 							try
 							{
-								if (screenshotFile != null && !screenshotFile.delete())
-									System.err.println((new StringBuilder()).append("couldn't delete ").append(screenshotFile.getCanonicalPath()).toString());
 								if (saveGameFile != null && !saveGameFile.delete())
 									System.err.println((new StringBuilder()).append("couldn't delete ").append(saveGameFile.getCanonicalPath()).toString());
 							} catch (final IOException ioe)
@@ -196,17 +173,28 @@
 							progressWindow.removeAll();
 							progressWindow.dispose();
 							delegate.setHasPostedTurnSummary(postOk);
-							m_postButton.setEnabled(!postOk);
-							if (postOk)
-								JOptionPane.showMessageDialog(m_frame, message, "Turn Summary Posted", 1);
+
+							final boolean finalPostOk = postOk;
+							final String finalMessage = sb.toString();
+							delegate.setHasPostedTurnSummary(postOk);
+							Runnable runnable = new Runnable()
+							{
+								public void run()
+								{
+									m_postButton.setEnabled(!finalPostOk);
+									if (finalPostOk)
+										JOptionPane.showMessageDialog(m_frame, finalMessage, "Turn Summary Posted", JOptionPane.INFORMATION_MESSAGE);
-							else
+									else
-								JOptionPane.showMessageDialog(m_frame, message, "Turn Summary Posted", 0);
+										JOptionPane.showMessageDialog(m_frame, finalMessage, "Turn Summary Posted", JOptionPane.ERROR_MESSAGE);
-						}
-					};
+								}
+							};
-					(new Thread(t)).start();
-					return;
+							SwingUtilities.invokeLater(runnable);
-				}
+						}
+					};
+					// start a new thread for posting the summary.
+					new Thread(t).start();
-			}
+				}
+			}
 		};
 		m_includeTerritoryAction = new AbstractAction("Include territory summary")
 		{
@@ -240,7 +228,30 @@
 		m_includeProductionCheckbox = new JCheckBox(m_includeProductionAction);
 		m_showDetailsCheckbox = new JCheckBox(m_showDetailsAction);
 	}
-	
+
+	private int getRound()
+	{
+		int round = 0;
+		final Object pathFromRoot[] = getData().getHistory().getLastNode().getPath();
+		final Object arr$[] = pathFromRoot;
+		final int len$ = arr$.length;
+		int i$ = 0;
+		do
+		{
+			if (i$ >= len$)
+				break;
+			final Object pathNode = arr$[i$];
+			final HistoryNode curNode = (HistoryNode) pathNode;
+			if (curNode instanceof Round)
+			{
+				round = ((Round) curNode).getRoundNo();
+				break;
+			}
+			i$++;
+		} while (true);
+		return round;
+	}
+
 	@Override
 	public void display(final PlayerID id)
 	{
@@ -277,17 +288,18 @@
 		m_frame = frame;
 		m_bridge = bridge;
 		// Nothing to do if there are no PBEM messengers
-		m_poster = new PBEMMessagePoster(getData());
+		m_poster = new PBEMMessagePoster(getData(), getCurrentPlayer(), getRound());
 		if (!m_poster.hasMessengers())
 			return;
-		m_historyLog = new HistoryLog();
-		updateHistoryLog();
+
 		final IAbstractEndTurnDelegate delegate = (IAbstractEndTurnDelegate) m_bridge.getRemote();
 		final boolean hasPosted = delegate.getHasPostedTurnSummary();
 		SwingUtilities.invokeLater(new Runnable()
 		{
 			public void run()
 			{
+				m_historyLog = new HistoryLog();
+				updateHistoryLog();
 				// only show widgets if there are PBEM messengers
 				removeAll();
 				add(m_actionLabel);
Index: src/games/strategy/engine/framework/IGameLoader.java
===================================================================
--- src/games/strategy/engine/framework/IGameLoader.java	(revision 3313)
+++ src/games/strategy/engine/framework/IGameLoader.java	(revision )
@@ -17,7 +17,6 @@
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.IPBEMMessenger;
 
 import java.util.Map;
 import java.util.Set;
@@ -59,11 +58,6 @@
 	public void startGame(IGame game, Set<IGamePlayer> players) throws Exception;
 	
 	/**
-	 * Get PBEM messengers for Turn Summary notification
-	 */
-	public IPBEMMessenger[] getPBEMMessengers();
-	
-	/**
 	 * Get the type of the display
 	 * 
 	 * @return an interface that extends IChannelSubscrobor
Index: src/games/strategy/engine/pbem/IPBEMScreenshotMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/IPBEMScreenshotMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/IPBEMScreenshotMessenger.java	(revision 3313)
@@ -1,24 +0,0 @@
-/*
- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-package games.strategy.engine.pbem;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface IPBEMScreenshotMessenger extends IPBEMMessenger
-{
-	public boolean postScreenshot(String fname, InputStream is) throws IOException;
-	
-	public String getScreenshotRef();
-}
Index: src/games/strategy/engine/framework/startup/ui/PBEMSetupPanel.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/PBEMSetupPanel.java	(revision 3313)
+++ src/games/strategy/engine/framework/startup/ui/PBEMSetupPanel.java	(revision )
@@ -1,804 +1,358 @@
 package games.strategy.engine.framework.startup.ui;
 
 import games.strategy.engine.data.GameData;
-import games.strategy.engine.framework.GameRunner;
+import games.strategy.engine.framework.startup.LocalBeanCache;
 import games.strategy.engine.framework.startup.launcher.ILauncher;
 import games.strategy.engine.framework.startup.launcher.LocalLauncher;
 import games.strategy.engine.framework.startup.mc.GameSelectorModel;
-import games.strategy.engine.pbem.IPBEMMessenger;
-import games.strategy.engine.pbem.IPBEMSaveGameMessenger;
-import games.strategy.engine.pbem.IPBEMScreenshotMessenger;
-import games.strategy.engine.pbem.IPBEMTurnSummaryMessenger;
-import games.strategy.engine.pbem.NullPBEMMessenger;
-import games.strategy.engine.pbem.PBEMMessagePoster;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+import games.strategy.engine.framework.startup.ui.editors.SelectAndViewEditor;
+import games.strategy.engine.pbem.*;
 import games.strategy.engine.random.IRemoteDiceServer;
+import games.strategy.engine.random.InternalDiceServer;
 import games.strategy.engine.random.PBEMDiceRoller;
 import games.strategy.engine.random.PropertiesDiceRoller;
-import games.strategy.ui.ProgressWindow;
-import games.strategy.util.Util;
+import games.strategy.triplea.pbem.AxisAndAlliesForumPoster;
 
-import java.awt.Color;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.Insets;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Properties;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+import java.util.List;
 
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPasswordField;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.SwingUtilities;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.Document;
-import javax.swing.text.PlainDocument;
-
+/**
+ * A panel for setting up Play by Email/Forum.
+ * This panel listens to the GameSelectionModel so it can refresh when a new (save) game is loaded
+ * The MainPanel also listens to this panel, and we notify it through the notifyObservers()
+ */
 public class PBEMSetupPanel extends SetupPanel implements Observer
 {
-	private static final String EMAIL_1_PROP_NAME = "games.strategy.engine.framework.ui.PBEMStartup.EMAIL2";
-	private static final String EMAIL_2_PROP_NAME = "games.strategy.engine.framework.ui.PBEMStartup.EMAIL1";
-	private static final String EMAIL_ID_PROP_NAME = "games.strategy.engine.framework.ui.PBEMStartup.ID";
-	private final JTextField m_email1TextField = new JTextField();
-	private final JTextField m_email2TextField = new JTextField();
-	private final JTextField m_gameIDTextField = new JTextField();
-	private final JLabel m_email1Label = new JLabel();
-	private final JLabel m_email2Label = new JLabel();
-	private final JLabel m_gameIDLabel = new JLabel();
-	private final JButton m_testDiceyButton = new JButton();
-	private final JTextArea m_instructionsText = new JTextArea();
-	private final JComboBox m_diceServers = new JComboBox();
-	private final JButton m_testPostButton = new JButton();
-	private final JButton m_viewPostButton = new JButton();
-	private final JComboBox m_turnSummaryMsgrs = new JComboBox();
-	private final JComboBox m_screenshotMsgrs = new JComboBox();
-	private final JComboBox m_saveGameMsgrs = new JComboBox();
-	private final JTextField m_turnSummaryMsgrLogin = new JTextField(20);
-	private final JPasswordField m_turnSummaryMsgrPassword = new JPasswordField(20);
-	private final JTextField m_screenshotMsgrLogin = new JTextField(20);
-	private final JPasswordField m_screenshotMsgrPassword = new JPasswordField(20);
-	private final JTextField m_saveGameMsgrLogin = new JTextField(20);
-	private final JPasswordField m_saveGameMsgrPassword = new JPasswordField(20);
-	private final JLabel m_turnSummaryMsgrLabel = new JLabel("Post Turn Summary:");
-	private final JLabel m_turnSummaryMsgrLoginLabel = new JLabel("Login:");
-	private final JLabel m_turnSummaryMsgrPasswordLabel = new JLabel("Password:");
-	private final JLabel m_screenshotMsgrLabel = new JLabel("Post Screenshot File:");
-	private final JLabel m_screenshotMsgrLoginLabel = new JLabel("Login:");
-	private final JLabel m_screenshotMsgrPasswordLabel = new JLabel("Password:");
-	private final JLabel m_saveGameMsgrLabel = new JLabel("Post Save Game File:");
-	private final JLabel m_saveGameMsgrLoginLabel = new JLabel("Login:");
-	private final JLabel m_saveGameMsgrPasswordLabel = new JLabel("Password:");
-	private final JPanel m_topPanel = new JPanel();
-	private final Map<Object, Document> usernameMap = new HashMap<Object, Document>();
-	private final Map<Object, Document> passwordMap = new HashMap<Object, Document>();
+
+	private static final String DICE_ROLLER = "games.strategy.engine.random.IRemoteDiceServer";
+
 	private final GameSelectorModel m_gameSelectorModel;
-	
+
+	private SelectAndViewEditor m_diceServerEditor;
+	private SelectAndViewEditor m_forumPosterEditor;
+	private SelectAndViewEditor m_emailSenderEditor;
+
 	public PBEMSetupPanel(final GameSelectorModel model)
 	{
 		m_gameSelectorModel = model;
-		createComponents();
-		layoutComponents();
+
+		// register, so we get notified when the game model (GameData) changes (e.g if the user load a save game or selects another game)
+		m_gameSelectorModel.addObserver(this);
+
+		setLayout(new GridBagLayout());
+		setBorder(new EmptyBorder(10, 10, 10, 10)); // Empty border works as margin
+
+		m_diceServerEditor = new SelectAndViewEditor("Dice Server");
+		m_forumPosterEditor = new SelectAndViewEditor("Post to Forum");
+		m_emailSenderEditor = new SelectAndViewEditor("Provider");
+
+		int row = 0;
+		add(m_diceServerEditor, new GridBagConstraints(0, row, 1, 1, 1.0d, 0d, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 1, 0), 0, 0));
+		row++;
+
+		// the play by Forum settings
+		m_forumPosterEditor.setBorder(new TitledBorder("Play By Forum"));
+		int panelRow = 0;
+		add(m_forumPosterEditor, new GridBagConstraints(0, row, 2, 1, 1.0d, 0d, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 1, 0), 0, 0));
+
+
+		row++;
+		panelRow = 0;
+		JPanel emailPanel = new JPanel(new GridBagLayout());
+		emailPanel.setBorder(new TitledBorder("Play By Email"));
+		add(emailPanel, new GridBagConstraints(0, row, 1, 1, 1.0d, 0d, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 2, 0), 0, 0));
+		JTextArea emailInstructions = new JTextArea();
+		emailInstructions.setText("Select an email provider if you want to receive an email with the turn summary and save game\n" +
+				"You can provide a subject (player and round # is automatically added)\n" +
+				"You can provide multiple email addresses separated by space");
+		emailInstructions.setWrapStyleWord(true);
+		emailInstructions.setLineWrap(true);
+		emailPanel.add(emailInstructions, new GridBagConstraints(0, panelRow, 1, 1, 1.0d, 0d, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 2, 0), 0, 0));
+		panelRow++;
+		emailPanel.add(m_emailSenderEditor, new GridBagConstraints(0, panelRow, 1, 1, 1.0d, 0d, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 2, 0), 0, 0));
+
+
+
 		setupListeners();
-		setWidgetActivation();
-		if (model.getGameData() != null)
+		if (m_gameSelectorModel.getGameData() != null)
 		{
-			loadEmails(model.getGameData());
-			loadPBEMMessengers(model.getGameData());
+			loadAll();
 		}
 	}
-	
+
-	private void createComponents()
-	{
-		m_email1Label.setText("To:");
-		m_email2Label.setText("Cc:");
-		m_gameIDLabel.setText("ID:");
-		m_testDiceyButton.setText("Test Email");
-		m_testPostButton.setText("Test Post");
-		m_viewPostButton.setText("View Posts");
-		m_email2TextField.setText("");
-		m_email2TextField.setColumns(50);
-		m_email1TextField.setText("");
-		m_email1TextField.setColumns(50);
-		m_gameIDTextField.setText("");
-		m_gameIDTextField.setColumns(10);
-		m_instructionsText.setEditable(false);
-		m_instructionsText.setText("PBEM Properties");
-		m_instructionsText.setLineWrap(true);
-		m_instructionsText.setWrapStyleWord(true);
-		final DefaultComboBoxModel diceServerModel = new DefaultComboBoxModel();
-		populateDiceRollModel(diceServerModel);
-		m_diceServers.setModel(diceServerModel);
-		final DefaultComboBoxModel turnSummaryMsgrModel = new DefaultComboBoxModel();
-		final DefaultComboBoxModel screenshotMsgrModel = new DefaultComboBoxModel();
-		final DefaultComboBoxModel saveGameMsgrModel = new DefaultComboBoxModel();
-		final IPBEMMessenger[] messengers = m_gameSelectorModel.getGameData().getGameLoader().getPBEMMessengers();
-		final NullPBEMMessenger nullMsgr = new NullPBEMMessenger();
-		turnSummaryMsgrModel.addElement(nullMsgr);
-		screenshotMsgrModel.addElement(nullMsgr);
-		saveGameMsgrModel.addElement(nullMsgr);
-		for (final IPBEMMessenger m : messengers)
-		{
-			if (m instanceof IPBEMTurnSummaryMessenger)
-				turnSummaryMsgrModel.addElement(m);
-			if (m instanceof IPBEMScreenshotMessenger)
-				screenshotMsgrModel.addElement(m);
-			if (m instanceof IPBEMSaveGameMessenger)
-				saveGameMsgrModel.addElement(m);
-		}
-		m_turnSummaryMsgrs.setModel(turnSummaryMsgrModel);
-		m_screenshotMsgrs.setModel(screenshotMsgrModel);
-		m_saveGameMsgrs.setModel(saveGameMsgrModel);
-		// initialize document maps
-		Document d = new PlainDocument();
-		usernameMap.put(nullMsgr, d);
-		m_turnSummaryMsgrLogin.setDocument(d);
-		m_screenshotMsgrLogin.setDocument(d);
-		m_saveGameMsgrLogin.setDocument(d);
-		d = new PlainDocument();
-		passwordMap.put(nullMsgr, d);
-		m_turnSummaryMsgrPassword.setDocument(d);
-		m_screenshotMsgrPassword.setDocument(d);
-		m_saveGameMsgrPassword.setDocument(d);
-		m_instructionsText.setText("You can enter up to 5 addresses in the To: or CC: fields, seperating each address by a space." + "\n\nYou must enter an address in the To: field.");
-		m_instructionsText.setBackground(this.getBackground());
-	}
-	
+
-	private void populateDiceRollModel(final DefaultComboBoxModel model)
-	{
-		final File f = new File(GameRunner.getRootFolder(), "dice_servers");
-		if (!f.exists())
-		{
-			throw new IllegalStateException("No dice server folder:" + f);
-		}
-		final java.util.List<Properties> propFiles = new ArrayList<Properties>();
-		final File[] files = f.listFiles();
-		for (final File file : files)
-		{
-			if (!file.isDirectory() && file.getName().endsWith(".properties"))
-			{
-				try
-				{
-					final Properties props = new Properties();
-					final FileInputStream fin = new FileInputStream(file);
-					try
-					{
-						props.load(fin);
-						propFiles.add(props);
-					} finally
-					{
-						fin.close();
-					}
-				} catch (final IOException e)
-				{
-					System.out.println("error reading file:" + file);
-					e.printStackTrace(System.out);
-				}
-			}
-		}
-		Collections.sort(propFiles, new Comparator<Properties>()
-		{
-			public int compare(final Properties o1, final Properties o2)
-			{
-				final int n1 = Integer.parseInt(o1.getProperty("order"));
-				final int n2 = Integer.parseInt(o2.getProperty("order"));
-				return n1 - n2;
-			}
-		});
-		for (final Properties prop : propFiles)
-		{
-			model.addElement(new PropertiesDiceRoller(prop));
-		}
-	}
-	
+
-	private void layoutComponents()
-	{
-		this.setLayout(new GridBagLayout());
-		layoutTopPanel();
-		this.add(m_topPanel, new GridBagConstraints(0, 0, 4, 2, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
-		final GridBagConstraints labelConstraint = new GridBagConstraints();
-		labelConstraint.anchor = GridBagConstraints.EAST;
-		labelConstraint.insets = new Insets(5, 0, 0, 0);
-		final GridBagConstraints widgetConstraint = new GridBagConstraints();
-		widgetConstraint.anchor = GridBagConstraints.WEST;
-		widgetConstraint.gridwidth = GridBagConstraints.REMAINDER;
-		widgetConstraint.fill = GridBagConstraints.HORIZONTAL;
-		widgetConstraint.weightx = 0.20000000000000001D;
-		widgetConstraint.insets = new Insets(5, 0, 0, 0);
-		final GridBagConstraints diceServerConstraint = new GridBagConstraints();
-		diceServerConstraint.anchor = GridBagConstraints.NORTHWEST;
-		diceServerConstraint.gridwidth = GridBagConstraints.REMAINDER;
-		diceServerConstraint.fill = GridBagConstraints.HORIZONTAL;
-		diceServerConstraint.insets = new Insets(5, 0, 0, 0);
-		final JPanel diceServer = new JPanel(new GridBagLayout());
-		diceServer.add(new JLabel("Dice Server:"), labelConstraint);
-		diceServer.add(m_diceServers, widgetConstraint);
-		// hide until we are ready
-		this.add(diceServer, new GridBagConstraints(0, 5, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 15, 0, 0), 0, 0));
-		this.add(m_testDiceyButton, new GridBagConstraints(2, 5, 1, 1, 0.2, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(15, 5, 0, 5), 0, 0));
-		final JPanel pbemMsgrPanel = new JPanel(new GridBagLayout());
-		// turn summary
-		pbemMsgrPanel.add(m_turnSummaryMsgrLabel, labelConstraint);
-		pbemMsgrPanel.add(m_turnSummaryMsgrs, widgetConstraint);
-		pbemMsgrPanel.add(m_turnSummaryMsgrLoginLabel, labelConstraint);
-		pbemMsgrPanel.add(m_turnSummaryMsgrLogin, widgetConstraint);
-		pbemMsgrPanel.add(m_turnSummaryMsgrPasswordLabel, labelConstraint);
-		pbemMsgrPanel.add(m_turnSummaryMsgrPassword, widgetConstraint);
-		// screenshot
-		pbemMsgrPanel.add(m_screenshotMsgrLabel, labelConstraint);
-		pbemMsgrPanel.add(m_screenshotMsgrs, widgetConstraint);
-		pbemMsgrPanel.add(m_screenshotMsgrLoginLabel, labelConstraint);
-		pbemMsgrPanel.add(m_screenshotMsgrLogin, widgetConstraint);
-		pbemMsgrPanel.add(m_screenshotMsgrPasswordLabel, labelConstraint);
-		pbemMsgrPanel.add(m_screenshotMsgrPassword, widgetConstraint);
-		// savegame
-		pbemMsgrPanel.add(m_saveGameMsgrLabel, labelConstraint);
-		pbemMsgrPanel.add(m_saveGameMsgrs, widgetConstraint);
-		pbemMsgrPanel.add(m_saveGameMsgrLoginLabel, labelConstraint);
-		pbemMsgrPanel.add(m_saveGameMsgrLogin, widgetConstraint);
-		pbemMsgrPanel.add(m_saveGameMsgrPasswordLabel, labelConstraint);
-		pbemMsgrPanel.add(m_saveGameMsgrPassword, widgetConstraint);
-		this.add(pbemMsgrPanel, new GridBagConstraints(0, 6, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(10, 15, 0, 0), 0, 0));
-		this.add(m_viewPostButton, new GridBagConstraints(2, 6, 1, 1, 0.2, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(15, 5, 0, 5), 0, 0));
-		this.add(m_testPostButton, new GridBagConstraints(2, 6, 1, 1, 0.2, 0, GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));
-	}
-	
+
-	private void layoutTopPanel()
-	{
-		m_topPanel.removeAll();
-		m_topPanel.setLayout(new GridBagLayout());
-		if (sendsEmail())
-		{
-			m_topPanel.add(m_instructionsText, new GridBagConstraints(0, 0, 5, 1, 0.0, 0.2, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
-			m_topPanel.add(m_email1TextField, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 5), 0, 0));
-			// this.add(m_email1Label, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 20,
-			m_topPanel.add(m_email1Label, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, new Insets(0, 20, 0, 5), 0, 0));
-			m_topPanel.add(m_email2TextField, new GridBagConstraints(1, 3, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 5), 0, 0));
-			m_topPanel.add(m_email2Label, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 20, 0, 5), 0, 0));
-			m_topPanel.add(m_gameIDTextField, new GridBagConstraints(1, 4, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 5), 0, 0));
-			m_topPanel.add(m_gameIDLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 20, 0, 5), 0, 0));
-		}
-		else
-		{
-			String text = ((PropertiesDiceRoller) getDiceServer()).getWebText();
-			if (text == null)
-				text = "The author of the properties file should set web.text property";
-			final JTextArea webText = new JTextArea();
-			webText.setEditable(false);
-			webText.setText("PBEM Properties");
-			webText.setLineWrap(true);
-			webText.setWrapStyleWord(true);
-			webText.setText(text);
-			webText.setBackground(getBackground());
-			m_topPanel.add(webText, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.2, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(0, 20, 0, 20), 0, 0));
-			m_topPanel.add(new JPanel(), new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
-		}
-		revalidate();
-	}
-	
+
-	private boolean sendsEmail()
-	{
-		return ((PropertiesDiceRoller) getDiceServer()).sendsEmail();
-	}
-	
+
-	private IRemoteDiceServer getDiceServer()
+	private void setupListeners()
 	{
-		return (IRemoteDiceServer) m_diceServers.getSelectedItem();
-	}
+		// subscribe to editor changes, so we can notify the MainPanel
+		m_diceServerEditor.addPropertyChangeListener(new NotifyingPropertyChangeListener());
+		m_forumPosterEditor.addPropertyChangeListener(new NotifyingPropertyChangeListener());
+		m_emailSenderEditor.addPropertyChangeListener(new NotifyingPropertyChangeListener());
-	
+
-	void testEmail()
-	{
-		final PBEMDiceRoller random = new PBEMDiceRoller(m_email1TextField.getText(), m_email2TextField.getText(), getGameID(), getDiceServer(), null);
-		random.test();
+
+
 	}
-	
+
-	void viewPostedItems()
+	private void loadAll()
 	{
-		final IPBEMTurnSummaryMessenger msgr = getTurnSummaryMessenger();
-		msgr.setGameId(m_gameIDTextField.getText().trim());
-		msgr.viewPosted();
+		loadDiceServer(m_gameSelectorModel.getGameData());
+		loadForumPosters(m_gameSelectorModel.getGameData());
+		loadEmailSender(m_gameSelectorModel.getGameData());
 	}
-	
+
-	void testPost()
+
+
+	/**
+	 * Loads the to/cc email and forum ID if the game is a save game
+	 *
+	 * @param data the game data
+	 */
+	private void loadDiceServer(final GameData data)
 	{
-		final ProgressWindow progressWindow = new ProgressWindow(MainFrame.getInstance(), "Testing Post...");
-		progressWindow.setVisible(true);
-		final GameData gameData = new GameData();
-		// populate PBEM Messengers from UI fields and store
-		// the messengers in gameData
-		storePBEMMessengers(gameData);
-		// create a new message poster with no IDelegateHistoryWriter
-		final PBEMMessagePoster poster = new PBEMMessagePoster(gameData);
-		poster.postTestData();
-		final String screenshotRef = poster.getScreenshotRef();
-		final String saveGameRef = poster.getSaveGameRef();
-		final String turnSummaryRef = poster.getTurnSummaryRef();
-		String message = "";
-		if (screenshotRef != null)
-			message += "Test Screenshot: " + screenshotRef + "\n";
-		if (saveGameRef != null)
-			message += "Test Save Game: " + saveGameRef + "\n";
-		if (turnSummaryRef != null)
-			message += "Test turn summary: " + turnSummaryRef + "\n";
-		SwingUtilities.invokeLater(new Runnable()
-		{
-			public void run()
-			{
-				progressWindow.setVisible(false);
-				progressWindow.removeAll();
-				progressWindow.dispose();
-			}
-		});
-		JOptionPane.showMessageDialog(MainFrame.getInstance(), message, "Test Turn Summary Post", JOptionPane.INFORMATION_MESSAGE);
-	}
-	
+
-	private void setupListeners()
+		List<IRemoteDiceServer> diceRollers = new ArrayList<IRemoteDiceServer>(PropertiesDiceRoller.loadFromFile());
+		diceRollers.add(new InternalDiceServer());
+
+		for (IRemoteDiceServer diceRoller : diceRollers)
-	{
+		{
-		m_gameSelectorModel.addObserver(this);
-		m_testDiceyButton.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				testEmail();
+			IRemoteDiceServer cached = (IRemoteDiceServer) LocalBeanCache.getInstance().getSerializable(diceRoller.getDisplayName());
+			if (cached != null) {
+				// Since dice rollers are serialized with their Properties, and the user may have changed these in the files
+				// we only use the gameId and email from the cached version
+				diceRoller.setCcAddress(cached.getCcAddress());
+				diceRoller.setToAddress(cached.getToAddress());
+				diceRoller.setGameId(cached.getGameId());
 			}
-		});
-		m_testPostButton.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				final Runnable t = new Runnable()
-				{
-					public void run()
-					{
-						testPost();
-					}
+		}
-				};
-				(new Thread(t)).start();
-			}
-		});
-		m_viewPostButton.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				final Runnable t = new Runnable()
-				{
-					public void run()
-					{
-						viewPostedItems();
-					}
-				};
-				(new Thread(t)).start();
-			}
-		});
-		// add document listeners for text-changed events
-		final DocumentListener docListener = new DocumentListener()
-		{
-			public void changedUpdate(final DocumentEvent e)
-			{
-				notifyObservers();
-			}
-			
+
-			public void insertUpdate(final DocumentEvent e)
-			{
-				notifyObservers();
-			}
+		m_diceServerEditor.setBeans(diceRollers);
-			
+
-			public void removeUpdate(final DocumentEvent e)
+		if (m_gameSelectorModel.isSavedGame())
-			{
+		{
-				notifyObservers();
-			}
-		};
-		m_turnSummaryMsgrs.addActionListener(new ActionListener()
+			// get the dice roller from the save game, if any
+			IRemoteDiceServer roller = (IRemoteDiceServer) data.getProperties().get(DICE_ROLLER);
+			if (roller != null)
-		{
+			{
-			public void actionPerformed(final ActionEvent e)
-			{
-				final IPBEMMessenger msgr = (IPBEMMessenger) ((JComboBox) e.getSource()).getSelectedItem();
-				m_turnSummaryMsgrLogin.setEnabled(msgr.getNeedsUsername());
-				m_turnSummaryMsgrLoginLabel.setEnabled(msgr.getNeedsUsername());
-				m_turnSummaryMsgrPassword.setEnabled(msgr.getNeedsPassword());
-				m_turnSummaryMsgrPasswordLabel.setEnabled(msgr.getNeedsPassword());
-				// update maps
-				final Object key = m_turnSummaryMsgrs.getSelectedItem();
-				Document d = usernameMap.get(key);
-				if (d == null)
-				{
-					d = new PlainDocument();
-					d.addDocumentListener(docListener);
+				m_diceServerEditor.setSelectedBean(roller);
-				}
+			}
-				usernameMap.put(key, d);
-				m_turnSummaryMsgrLogin.setDocument(d);
-				d = passwordMap.get(key);
-				if (d == null)
-				{
-					d = new PlainDocument();
-					d.addDocumentListener(docListener);
-				}
+		}
-				passwordMap.put(key, d);
-				m_turnSummaryMsgrPassword.setDocument(d);
-				notifyObservers();
-			}
+	}
-		});
-		m_turnSummaryMsgrs.setSelectedIndex(m_turnSummaryMsgrs.getSelectedIndex());
-		m_screenshotMsgrs.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				final IPBEMMessenger msgr = (IPBEMMessenger) ((JComboBox) e.getSource()).getSelectedItem();
-				m_screenshotMsgrLogin.setEnabled(msgr.getNeedsUsername());
-				m_screenshotMsgrLoginLabel.setEnabled(msgr.getNeedsUsername());
-				m_screenshotMsgrPassword.setEnabled(msgr.getNeedsPassword());
-				m_screenshotMsgrPasswordLabel.setEnabled(msgr.getNeedsPassword());
-				final Object key = m_screenshotMsgrs.getSelectedItem();
-				Document d = usernameMap.get(key);
-				if (d == null)
-					d = new PlainDocument();
-				usernameMap.put(key, d);
-				m_screenshotMsgrLogin.setDocument(d);
-				d = passwordMap.get(key);
-				if (d == null)
-					d = new PlainDocument();
-				passwordMap.put(key, d);
-				m_screenshotMsgrPassword.setDocument(d);
-				notifyObservers();
-			}
-		});
-		m_screenshotMsgrs.setSelectedIndex(m_screenshotMsgrs.getSelectedIndex());
-		m_saveGameMsgrs.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				final IPBEMMessenger msgr = (IPBEMMessenger) ((JComboBox) e.getSource()).getSelectedItem();
-				m_saveGameMsgrLogin.setEnabled(msgr.getNeedsUsername());
-				m_saveGameMsgrLoginLabel.setEnabled(msgr.getNeedsUsername());
-				m_saveGameMsgrPassword.setEnabled(msgr.getNeedsPassword());
-				m_saveGameMsgrPasswordLabel.setEnabled(msgr.getNeedsPassword());
-				// update maps
-				final Object key = m_saveGameMsgrs.getSelectedItem();
-				Document d = usernameMap.get(key);
-				if (d == null)
-					d = new PlainDocument();
-				usernameMap.put(key, d);
-				m_saveGameMsgrLogin.setDocument(d);
-				d = passwordMap.get(key);
-				if (d == null)
-					d = new PlainDocument();
-				passwordMap.put(key, d);
-				m_saveGameMsgrPassword.setDocument(d);
-				notifyObservers();
-			}
-		});
-		m_saveGameMsgrs.setSelectedIndex(m_saveGameMsgrs.getSelectedIndex());
-		m_email1TextField.getDocument().addDocumentListener(docListener);
-		m_email2TextField.getDocument().addDocumentListener(docListener);
-		m_gameIDTextField.getDocument().addDocumentListener(docListener);
-		m_turnSummaryMsgrLogin.getDocument().addDocumentListener(docListener);
-		m_turnSummaryMsgrPassword.getDocument().addDocumentListener(docListener);
-		m_screenshotMsgrLogin.getDocument().addDocumentListener(docListener);
-		m_screenshotMsgrPassword.getDocument().addDocumentListener(docListener);
-		m_saveGameMsgrLogin.getDocument().addDocumentListener(docListener);
-		m_saveGameMsgrPassword.getDocument().addDocumentListener(docListener);
-		m_diceServers.addActionListener(new ActionListener()
-		{
-			public void actionPerformed(final ActionEvent e)
-			{
-				setWidgetActivation();
-				notifyObservers();
-			}
-		});
-	}
-	
+
-	private String getGameID()
-	{
-		return m_gameIDTextField.getText();
-	}
-	
+
-	private String getEmail1()
+	/**
+	 * Load the Forum poster that are stored in the GameData, and select it in the list.
+	 * Sensitive information such as passwords are not stored in save games, so the are loaded from the local Serialization Cache
+	 *
+	 * @param data the game data
+	 */
+	private void loadForumPosters(final GameData data)
 	{
-		return m_email1TextField.getText();
-	}
-	
+
-	private String getEmail2()
-	{
-		return m_email2TextField.getText();
-	}
+		// get the forum posters,
+		List<IForumPoster> forumPosters = new ArrayList<IForumPoster>();
+		forumPosters.add((IForumPoster) findCachedOrCreateNew(NullForumPoster.class));
+		forumPosters.add((IForumPoster) findCachedOrCreateNew(AxisAndAlliesForumPoster.class));
+		m_forumPosterEditor.setBeans(forumPosters);
-	
+
-	private IPBEMTurnSummaryMessenger getTurnSummaryMessenger()
+		// now get the poster stored in the save game
+		IForumPoster forumPoster = (IForumPoster) data.getProperties().get(PBEMMessagePoster.FORUM_POSTER_PROP_NAME);
+		if (forumPoster != null)
-	{
+		{
-		final IPBEMMessenger msgr = (IPBEMMessenger) m_turnSummaryMsgrs.getSelectedItem();
-		if (msgr instanceof IPBEMTurnSummaryMessenger)
-			return (IPBEMTurnSummaryMessenger) msgr;
-		else
-			return null;
-	}
-	
-	private IPBEMScreenshotMessenger getScreenshotMessenger()
+			// if we have a cached version, use the credentials from this, as each player has different forum login
+			IForumPoster cached = (IForumPoster) LocalBeanCache.getInstance().getSerializable(forumPoster.getClass().getCanonicalName());
+			if (cached != null)
-	{
+			{
-		final IPBEMMessenger msgr = (IPBEMMessenger) m_screenshotMsgrs.getSelectedItem();
-		if (msgr instanceof IPBEMScreenshotMessenger)
-			return (IPBEMScreenshotMessenger) msgr;
-		else
-			return null;
+				forumPoster.setUsername(cached.getUsername());
+				forumPoster.setPassword(cached.getPassword());
-	}
+			}
+			m_forumPosterEditor.setSelectedBean(forumPoster);
+		}
+	}
-	
+
-	private IPBEMSaveGameMessenger getSaveGameMessenger()
+	private void loadEmailSender(GameData data)
 	{
-		final IPBEMMessenger msgr = (IPBEMMessenger) m_saveGameMsgrs.getSelectedItem();
-		if (msgr instanceof IPBEMSaveGameMessenger)
-			return (IPBEMSaveGameMessenger) msgr;
-		else
-			return null;
-	}
+		// The list of email, either loaded from cache or created
+		List<IEmailSender> emailSenders = new ArrayList<IEmailSender>();
+		emailSenders.add((IEmailSender) findCachedOrCreateNew(NullEmailSender.class));
+		emailSenders.add((IEmailSender) findCachedOrCreateNew(GmailEmailSender.class));
+		emailSenders.add((IEmailSender) findCachedOrCreateNew(HotmailEmailSender.class));
+		emailSenders.add((IEmailSender) findCachedOrCreateNew(GenericEmailSender.class));
+		m_emailSenderEditor.setBeans(emailSenders);
-	
+
-	private void loadEmails(final GameData data)
+		// now get the sender from the save game, update it with credentials from the cache, and set it
+		IEmailSender sender = (IEmailSender) data.getProperties().get(PBEMMessagePoster.EMAIL_SENDER_PROP_NAME);
+		if (sender != null)
-	{
+		{
-		if (m_gameSelectorModel.isSavedGame())
+			IEmailSender cached = (IEmailSender) LocalBeanCache.getInstance().getSerializable(sender.getClass().getCanonicalName());
+			if (cached != null)
-		{
+			{
-			if (data.getProperties().get(EMAIL_1_PROP_NAME) != null)
-			{
-				m_email1TextField.setText(data.getProperties().get(EMAIL_1_PROP_NAME).toString());
+				sender.setUserName(cached.getUserName());
+				sender.setPassword(cached.getPassword());
 			}
-			if (data.getProperties().get(EMAIL_2_PROP_NAME) != null)
-			{
-				m_email2TextField.setText(data.getProperties().get(EMAIL_2_PROP_NAME).toString());
+			m_emailSenderEditor.setSelectedBean(sender);
-			}
+		}
-			if (data.getProperties().get(EMAIL_ID_PROP_NAME) != null)
-			{
-				m_gameIDTextField.setText(data.getProperties().get(EMAIL_ID_PROP_NAME).toString());
-			}
+	}
-		}
-	}
-	
+
-	private void replaceOrAddComboBoxItem(final JComboBox comboBox, final Object customObj)
+	/**
+	 * finds a cached instance of the give type. If a cached version is not available a new one is created
+	 * @param theClassType the type of class
+	 * @return a IBean either loaded from the cache or created
+	 */
+	private IBean findCachedOrCreateNew(Class<? extends IBean> theClassType)
 	{
-		if (customObj != null)
+		IBean cached = LocalBeanCache.getInstance().getSerializable(theClassType.getCanonicalName());
+		if (cached == null) {
+			try
-		{
+			{
-			Object defaultObj;
-			int i;
-			for (i = 0; (defaultObj = comboBox.getItemAt(i)) != null && !defaultObj.toString().equals(customObj.toString()); i++)
-				defaultObj = null;
-			if (defaultObj != null)
+				cached = theClassType.newInstance();
+			} catch (Exception e)
 			{
-				((DefaultComboBoxModel) comboBox.getModel()).getIndexOf(defaultObj);
-				comboBox.removeItemAt(i);
-				comboBox.insertItemAt(customObj, i);
+				throw new RuntimeException("Bean of type " + theClassType + " doesn't have public default constructor, error: " + e.getMessage());
 			}
-			else
-			{
-				comboBox.addItem(customObj);
-			}
+		}
-			comboBox.setSelectedItem(customObj);
+		return cached;
-		}
+	}
-	}
-	
+
-	private void loadPBEMMessengers(final GameData data)
-	{
-		// TODO: store username, and password fields in user data
-		// Let a temporary poster do the loading
-		final PBEMMessagePoster poster = new PBEMMessagePoster(data);
-		// We now have two instances of these messengers - deserialized instance and GameLoader instance.
-		// Need to merge the two.
-		if (!(m_turnSummaryMsgrs.getSelectedItem() instanceof IPBEMTurnSummaryMessenger))
-			replaceOrAddComboBoxItem(m_turnSummaryMsgrs, poster.getTurnSummaryMessenger());
-		if (!(m_screenshotMsgrs.getSelectedItem() instanceof IPBEMScreenshotMessenger))
-			replaceOrAddComboBoxItem(m_screenshotMsgrs, poster.getScreenshotMessenger());
-		if (!(m_saveGameMsgrs.getSelectedItem() instanceof IPBEMSaveGameMessenger))
-			replaceOrAddComboBoxItem(m_saveGameMsgrs, poster.getSaveGameMessenger());
-	}
-	
+
-	private void setWidgetActivation()
-	{
-		layoutTopPanel();
-	}
-	
 	@Override
 	public void cancel()
 	{
 		m_gameSelectorModel.deleteObserver(this);
 	}
-	
+
 	@Override
 	public boolean canGameStart()
 	{
-		boolean canStart = true;
-		boolean canTestEmail = true;
-		boolean canTestPost = true;
-		boolean canViewPost = true;
-		IPBEMMessenger turnSummaryMsgr = null;
-		IPBEMMessenger screenshotMsgr = null;
-		IPBEMMessenger saveGameMsgr = null;
-		// verify the emails
-		if (sendsEmail())
-		{
-			if (m_email1TextField.getText().trim().equals("") || !Util.isMailValid(m_email1TextField.getText()))
-			{
-				m_email1Label.setForeground(Color.RED);
-				canTestEmail = false;
-				canStart = false;
+		boolean diceServerValid = m_diceServerEditor.isInputValid();
+		boolean summaryValid = m_forumPosterEditor.isInputValid();
+		boolean emailValid = m_emailSenderEditor.isInputValid();
+
+		return diceServerValid &&
+				summaryValid &&
+				emailValid &&
+				m_gameSelectorModel.getGameData() != null;
+
-			}
+	}
-			else
+
+
+	@Override
+	public void postStartGame()
-			{
+	{
-				m_email1Label.setForeground(Color.BLACK);
-			}
-			if (!Util.isMailValid(m_email2TextField.getText()))
+		// // store the dice server
+		final GameData data = m_gameSelectorModel.getGameData();
+		data.getProperties().set(DICE_ROLLER, m_diceServerEditor.getBean());
+
+		// store the Turn Summary Poster
+		IForumPoster poster = (IForumPoster) m_forumPosterEditor.getBean();
+		if (poster != null)
-			{
+		{
-				m_email2Label.setForeground(Color.RED);
-				canTestEmail = false;
-				canStart = false;
+			IForumPoster summaryPoster = poster;
+			// clone the poster, the remove sensitive info, and put the clone into the game data
+			// this was the sensitive info is not stored in the save game, but the user cache still has the password
+			summaryPoster = summaryPoster.doClone();
+			summaryPoster.clearSensitiveInfo();
+			data.getProperties().set(PBEMMessagePoster.FORUM_POSTER_PROP_NAME, summaryPoster);
-			}
+		}
-			else
+
+		// store the email poster
+		IEmailSender sender = (IEmailSender) m_emailSenderEditor.getBean();
+		if (sender != null)
-			{
+		{
-				m_email2Label.setForeground(Color.BLACK);
+			// create a clone, delete the sensitive information in the clone, and use it in the game
+			// the locally cached version still has the password so the user doesn't have to enter it every time
+			sender = sender.doClone();
+			sender.clearSensitiveInfo();
+			data.getProperties().set(PBEMMessagePoster.EMAIL_SENDER_PROP_NAME, sender);
-			}
-		}
+		}
+	}
-		// verify PBEM messenger fields
-		turnSummaryMsgr = (IPBEMMessenger) m_turnSummaryMsgrs.getSelectedItem();
-		if (turnSummaryMsgr.getNeedsUsername() && m_turnSummaryMsgrLogin.getText().trim().equals(""))
-		{
-			m_turnSummaryMsgrLoginLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_turnSummaryMsgrLoginLabel.setForeground(Color.BLACK);
-		}
-		if (turnSummaryMsgr.getNeedsPassword() && (new String(m_turnSummaryMsgrPassword.getPassword())).trim().equals(""))
-		{
-			m_turnSummaryMsgrPasswordLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_turnSummaryMsgrPasswordLabel.setForeground(Color.BLACK);
-		}
-		screenshotMsgr = (IPBEMMessenger) m_screenshotMsgrs.getSelectedItem();
-		if (screenshotMsgr.getNeedsUsername() && m_screenshotMsgrLogin.getText().trim().equals(""))
-		{
-			m_screenshotMsgrLoginLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_screenshotMsgrLoginLabel.setForeground(Color.BLACK);
-		}
-		if (screenshotMsgr.getNeedsPassword() && (new String(m_screenshotMsgrPassword.getPassword())).trim().equals(""))
-		{
-			m_screenshotMsgrPasswordLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canViewPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_screenshotMsgrPasswordLabel.setForeground(Color.BLACK);
-		}
-		saveGameMsgr = (IPBEMMessenger) m_saveGameMsgrs.getSelectedItem();
-		if (saveGameMsgr.getNeedsUsername() && m_saveGameMsgrLogin.getText().trim().equals(""))
-		{
-			m_saveGameMsgrLoginLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_saveGameMsgrLoginLabel.setForeground(Color.BLACK);
-		}
-		if (saveGameMsgr.getNeedsPassword() && (new String(m_saveGameMsgrPassword.getPassword())).trim().equals(""))
-		{
-			m_saveGameMsgrPasswordLabel.setForeground(Color.RED);
-			canTestPost = false;
-			canStart = false;
-		}
-		else
-		{
-			m_saveGameMsgrPasswordLabel.setForeground(Color.BLACK);
-		}
-		if (turnSummaryMsgr instanceof NullPBEMMessenger && screenshotMsgr instanceof NullPBEMMessenger && saveGameMsgr instanceof NullPBEMMessenger)
-		{
-			canTestPost = false;
-			m_gameIDLabel.setForeground(Color.BLACK);
-		}
-		else
-		{
-			// if posting, require game id
-			if (m_gameIDTextField.getText().trim().equals(""))
-			{
-				m_gameIDLabel.setForeground(Color.RED);
-				canTestPost = false;
-				canViewPost = false;
-				canStart = false;
-			}
-			else
-			{
-				m_gameIDLabel.setForeground(Color.BLACK);
-			}
-		}
-		if (!turnSummaryMsgr.getCanViewPosted())
-			canViewPost = false;
-		m_testDiceyButton.setEnabled(canTestEmail);
-		m_testPostButton.setEnabled(canTestPost);
-		m_viewPostButton.setEnabled(canViewPost);
-		return canStart && (m_gameSelectorModel.getGameData() != null);
-	}
-	
+
-	private void storeEmails(final GameData data)
+	/**
+	 * Is called in response to the GameSelectionModel being updated. It means the we have to reload the form
+	 *
+	 * @param o   always null
+	 * @param arg always null
+	 */
+	public void update(final Observable o, final Object arg)
 	{
-		data.getProperties().set(EMAIL_1_PROP_NAME, getEmail1());
-		data.getProperties().set(EMAIL_2_PROP_NAME, getEmail2());
-		data.getProperties().set(EMAIL_ID_PROP_NAME, getGameID());
-	}
-	
+
-	private void storePBEMMessengers(final GameData gameData)
+		if (!SwingUtilities.isEventDispatchThread())
-	{
+		{
-		// let a temporary poster do the storing
-		final PBEMMessagePoster poster = new PBEMMessagePoster();
-		IPBEMMessenger msgr;
-		if ((msgr = getTurnSummaryMessenger()) != null)
+			SwingUtilities.invokeLater(new Runnable()
-		{
+			{
-			msgr.setUsername(m_turnSummaryMsgrLogin.getText().trim());
-			msgr.setPassword((new String(m_turnSummaryMsgrPassword.getPassword())).trim());
-			msgr.setGameId(m_gameIDTextField.getText().trim());
-		}
-		poster.setTurnSummaryMessenger(msgr);
-		if ((msgr = getScreenshotMessenger()) != null)
+				public void run()
-		{
+				{
-			msgr.setUsername(m_screenshotMsgrLogin.getText().trim());
-			msgr.setPassword((new String(m_screenshotMsgrPassword.getPassword())).trim());
-			msgr.setGameId(m_gameIDTextField.getText().trim());
+					loadAll();
-		}
+				}
-		poster.setScreenshotMessenger(msgr);
-		if ((msgr = getSaveGameMessenger()) != null)
+			});
+
+		} else
 		{
-			msgr.setUsername(m_saveGameMsgrLogin.getText().trim());
-			msgr.setPassword((new String(m_saveGameMsgrPassword.getPassword())).trim());
-			msgr.setGameId(m_gameIDTextField.getText().trim());
+			loadAll();
 		}
-		poster.setSaveGameMessenger(msgr);
-		poster.storeMessengers(gameData);
 	}
-	
+
+
 	@Override
-	public void postStartGame()
+	/**
+	 * Called when the user hits play
+	 */
+	public ILauncher getLauncher()
 	{
-		storeEmails(m_gameSelectorModel.getGameData());
-		storePBEMMessengers(m_gameSelectorModel.getGameData());
+		// update local cache and write to disk before game starts
+		IForumPoster poster = (IForumPoster) m_forumPosterEditor.getBean();
+		if (poster != null)
+		{
+			LocalBeanCache.getInstance().storeSerializable(poster.getClass().getCanonicalName(), poster);
-	}
-	
+		}
+
-	public void update(final Observable o, final Object arg)
+		IEmailSender sender = (IEmailSender) m_emailSenderEditor.getBean();
+		if (sender != null)
-	{
+		{
-		loadEmails(m_gameSelectorModel.getGameData());
-		loadPBEMMessengers(m_gameSelectorModel.getGameData());
+			LocalBeanCache.getInstance().storeSerializable(sender.getClass().getCanonicalName(), sender);
-	}
-	
+		}
+
-	@Override
-	public ILauncher getLauncher()
-	{
+		IRemoteDiceServer server = (IRemoteDiceServer) m_diceServerEditor.getBean();
+		LocalBeanCache.getInstance().storeSerializable(server.getDisplayName(), server);
+
+		LocalBeanCache.getInstance().writeToDisk();
+
+		// create local launcher
 		final String gameUUID = (String) m_gameSelectorModel.getGameData().getProperties().get(GameData.GAME_UUID);
-		final PBEMDiceRoller randomSource = new PBEMDiceRoller(getEmail1(), getEmail2(), getGameID(), getDiceServer(), gameUUID);
+		final PBEMDiceRoller randomSource = new PBEMDiceRoller((IRemoteDiceServer) m_diceServerEditor.getBean(), gameUUID);
 		final Map<String, String> playerTypes = new HashMap<String, String>();
 		final String playerType = m_gameSelectorModel.getGameData().getGameLoader().getServerPlayerTypes()[0];
 		for (final String playerName : m_gameSelectorModel.getGameData().getPlayerList().getNames())
 		{
 			playerTypes.put(playerName, playerType);
 		}
-		final LocalLauncher launcher = new LocalLauncher(m_gameSelectorModel, randomSource, playerTypes);
-		return launcher;
+
+ 		return new LocalLauncher(m_gameSelectorModel, randomSource, playerTypes);
 	}
+
+	//-----------------------------------------------------------------------
+	// inner classes
+	//-----------------------------------------------------------------------
+
+	/**
+	 * A property change listener that notify our observers
+	 */
+	private class NotifyingPropertyChangeListener implements PropertyChangeListener
+	{
+		public void propertyChange(PropertyChangeEvent evt)
+		{
+			notifyObservers();
-}
+		}
+	}
+
+
+}
Index: src/games/strategy/engine/framework/startup/ui/editors/validators/IValidator.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/validators/IValidator.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/validators/IValidator.java	(revision )
@@ -0,0 +1,21 @@
+package games.strategy.engine.framework.startup.ui.editors.validators;
+
+/**
+ * A simple interface for validating data from text fields
+ * @author Klaus Groenbaek
+ */
+public interface IValidator
+{
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Validates that the give input is valid
+	 * @param text the text to be validated
+	 * @return true if the data is valid
+	 */
+	boolean isValid(String text);
+
+}
Index: src/games/strategy/engine/pbem/IEmailSender.java
===================================================================
--- src/games/strategy/engine/pbem/IEmailSender.java	(revision )
+++ src/games/strategy/engine/pbem/IEmailSender.java	(revision )
@@ -0,0 +1,57 @@
+package games.strategy.engine.pbem;
+
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * An interface for sending emails from a PBEM (play by email ) game.
+ * Implementers must be serialized, as the sender is stored as part of the save game.
+ * It is also the job of the implementer to store the to address, host/port, credentials etc.
+ * @author Klaus Groenbaek
+ */
+public interface IEmailSender extends IBean
+{
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Sends an email with the given subject, optionally attaches a save game file.
+	 * The address, and credentials must be stored by the implementing class
+	 *
+	 * @param subject the subject of the email
+	 * @param htmlMessage the html email body
+	 * @param saveGame the savegame or null
+	 * @throws IOException if an error occurs
+	 */
+	void sendEmail(String subject, String htmlMessage, File saveGame, String saveGameName) throws IOException;
+
+	/**
+	 * Get the to email addresses configured for this sender.
+	 * May contain multiple email addresses separated by space ' '
+	 * @return the to addresses
+	 */
+	String getToAddress();
+
+	/**
+	 * Remove any sensitive information, like passwords before this object is saved in as a game properties
+	 */
+	void clearSensitiveInfo();
+
+	/**
+	 * Clones this instance.
+	 * @return return a clone
+	 */
+	public IEmailSender doClone();
+
+	String getUserName();
+
+	String getPassword();
+
+	void setUserName(String userName);
+
+	void setPassword(String password);
+}
Index: src/games/strategy/engine/pbem/IPBEMMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/IPBEMMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/IPBEMMessenger.java	(revision 3313)
@@ -1,39 +0,0 @@
-/*
- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-package games.strategy.engine.pbem;
-
-public interface IPBEMMessenger extends java.io.Serializable
-{
-	public String getName();
-	
-	public boolean getNeedsUsername();
-	
-	public boolean getNeedsPassword();
-	
-	public boolean getCanViewPosted();
-	
-	public String getGameId();
-	
-	public String getUsername();
-	
-	public String getPassword();
-	
-	public void setGameId(String gameId);
-	
-	public void setUsername(String username);
-	
-	public void setPassword(String password);
-	
-	public void viewPosted();
-}
Index: src/games/strategy/engine/pbem/NullPBEMMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/NullPBEMMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/NullForumPoster.java	(revision )
@@ -15,34 +15,34 @@
 
 import games.strategy.engine.data.Change;
 import games.strategy.engine.data.PlayerID;
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
 
-public class NullPBEMMessenger implements IPBEMMessenger
-{
-	public NullPBEMMessenger()
-	{
-	}
+import java.io.File;
-	
+
-	public String getName()
+/**
+ * A dummy forum poster, for when Forum posting is disabled
+ */
+public class NullForumPoster implements IForumPoster
-	{
+{
-		return "disabled";
-	}
+	private static final long serialVersionUID = 6465230505089142268L;
-	
+
-	public boolean getNeedsUsername()
+	public NullForumPoster()
 	{
-		return false;
 	}
 	
-	public boolean getNeedsPassword()
+	public String getDisplayName()
 	{
-		return false;
+		return "disabled";
 	}
 	
+
 	public boolean getCanViewPosted()
 	{
 		return false;
 	}
 	
-	public void setGameId(final String s)
+	public void setForumId(final String forumId)
 	{
 	}
 	
@@ -54,7 +54,7 @@
 	{
 	}
 	
-	public String getGameId()
+	public String getForumId()
 	{
 		return null;
 	}
@@ -72,7 +72,12 @@
 	public void viewPosted()
 	{
 	}
-	
+
+	public void clearSensitiveInfo()
+	{
+
+	}
+
 	public void gameStepChanged(final String stepName, final String delegateName, final PlayerID player, final int round, final String displayName)
 	{
 	}
@@ -80,10 +85,47 @@
 	public void gameDataChanged(final Change change)
 	{
 	}
-	
+
-	@Override
-	public String toString()
+	public boolean postTurnSummary(String summary)
 	{
-		return getName();
+		return false;
 	}
+
+	public String getTurnSummaryRef()
+	{
+		return null;
-}
+	}
+
+	public boolean getIncludeSaveGame()
+	{
+		return false;
+	}
+
+	public void setIncludeSaveGame(boolean include)
+	{
+	}
+
+	public void addSaveGame(File saveGame, String fileName)
+	{
+	}
+
+	public IForumPoster doClone()
+	{
+		return null;
+	}
+
+	public boolean supportsSaveGame()
+	{
+		return false;
+	}
+
+	public EditorPanel getEditor()
+	{
+		return null;
+	}
+
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == NullForumPoster.class;
+	}
+}
Index: src/games/strategy/engine/pbem/GenericEmailSender.java
===================================================================
--- src/games/strategy/engine/pbem/GenericEmailSender.java	(revision )
+++ src/games/strategy/engine/pbem/GenericEmailSender.java	(revision )
@@ -0,0 +1,359 @@
+package games.strategy.engine.pbem;
+
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.EmailSenderEditor;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A PBEM (play by email) sender that will email turn summary and save game
+ * This class is saved as a property as part of a save game
+ * This class has two password fields, one is transitive and used while the game is running, the other is 'cleared' when
+ * the game starts. This is done for security reasons so save games will not include passwords.
+ * The non-transitive password is used when the object is stored in the local cache
+ *
+ * @author Klaus Groenbaek
+ */
+public class GenericEmailSender implements IEmailSender
+{
+	//-----------------------------------------------------------------------
+	// class fields
+	//-----------------------------------------------------------------------
+	private static final long serialVersionUID = 4644748856027574157L;
+	/**
+	 *  a value to assign to the non-transitive password, as we can see that is was cleared
+	 */
+	private static final String USE_TRANSITIVE_PASSWORD = "d0a11f0f-96d3-4303-8875-4965aefb2ce4";
+
+	//-----------------------------------------------------------------------
+	// instance fields 
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Currently only message encryption is allowed. Later connect based encryption through SSL may be implementes
+ 	 */
+	public enum Encryption {NONE, TLS}
+	//-----------------------------------------------------------------------
+	// constructors 
+	//-----------------------------------------------------------------------
+
+	private long m_timeout = TimeUnit.SECONDS.toMillis(60);
+	private String m_subjectPrefix;
+	private String m_userName;
+	private String m_password;
+	private transient String m_transPassword;
+	private String m_toAddress;
+	private String m_host = "smptserver.example.com";
+	private int m_port = 25;
+	private Encryption m_encryption;
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	public void sendEmail(String subject, String htmlMessage, File saveGame, String saveGameName) throws IOException
+	{
+		// this is the last step and we create the email to send
+
+		if (m_toAddress == null)
+		{
+			throw new IOException("Could not send email, no To address configured");
+		}
+
+		Properties props = new Properties();
+		if (getUserName() != null)
+		{
+			props.put("mail.smtp.auth", "true");
+		}
+		if (m_encryption == Encryption.TLS) {
+			props.put("mail.smtp.starttls.enable", "true");
+			props.put("mail.smtp.starttls.required", "true");
+		}
+
+		props.put("mail.smtp.host", getHost());
+		props.put("mail.smtp.port", getPort());
+
+		props.put("mail.smtp.connectiontimeout", m_timeout);
+		props.put("mail.smtp.timeout", m_timeout);
+
+		String to = m_toAddress;
+		String from = "noreply@triplea.sourceforge.net";
+
+		//todo get the turn and player number from the game data
+
+		try
+		{
+			Session session = Session.getInstance(props, null);
+
+			MimeMessage mimeMessage = new MimeMessage(session);
+			// Build the message fields one by one:
+			// priority
+			mimeMessage.setHeader("X-Priority", "3 (Normal)");
+
+			// from
+			mimeMessage.setFrom(new InternetAddress(from));
+
+			// to address
+			StringTokenizer toAddresses = new StringTokenizer(to, " ", false);
+			while (toAddresses.hasMoreTokens())
+			{
+				mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddresses.nextToken().trim()));
+			}
+
+			// subject
+			mimeMessage.setSubject(m_subjectPrefix + " " + subject);
+
+			MimeBodyPart bodypart = new MimeBodyPart();
+			bodypart.setText(htmlMessage, "UTF-8");
+			bodypart.setHeader("Content-Type", "text/html");
+
+
+			if (saveGame != null)
+			{
+				Multipart multipart = new MimeMultipart();
+
+				multipart.addBodyPart(bodypart);
+				// add save game
+				FileInputStream fin = new FileInputStream(saveGame);
+				DataSource source = new ByteArrayDataSource(fin, "application/triplea");
+				BodyPart messageBodyPart = new MimeBodyPart();
+				messageBodyPart.setDataHandler(new DataHandler(source));
+				messageBodyPart.setFileName(saveGameName);
+				multipart.addBodyPart(messageBodyPart);
+
+				mimeMessage.setContent(multipart);
+			}
+
+			// date
+			try
+			{
+				mimeMessage.setSentDate(new Date());
+			} catch (Exception e)
+			{
+				// NoOp - the Date field is simply ignored in this case
+			}
+
+
+			Transport transport = session.getTransport("smtp");
+			if (getUserName() != null)
+			{
+				transport.connect(getHost(), getPort(), getUserName(), getPassword());
+			} else
+			{
+				transport.connect();
+			}
+
+
+			mimeMessage.saveChanges();
+			transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
+			transport.close();
+
+		} catch (MessagingException e)
+		{
+			throw new IOException(e.getMessage(), e);
+		}
+	}
+
+
+	/**
+	 * Get the user name used to login to the smtp server to send the email
+	 * @return the userName or null if no authentication is required
+	 */
+	public String getUserName()
+	{
+		return m_userName;
+	}
+
+	/**
+	 * Set the userName used for authentication with the smtp server
+	 * @param userName the userName or null if no authentication is required
+	 */
+	public void setUserName(String userName)
+	{
+		m_userName = userName;
+	}
+
+	/**
+	 * Get the password used to authenticate
+	 * @return the password or null
+	 */
+	public String getPassword()
+	{
+		if (USE_TRANSITIVE_PASSWORD.equals(m_password)) {
+			return m_transPassword;
+		}
+		return m_password;
+	}
+
+	/**
+	 * Set the password to authenticate with
+	 * @param password the password or null
+	 */
+	public void setPassword(String password)
+	{
+		m_password = password;
+		m_transPassword = password;
+	}
+
+	/**
+	 * Get the timeout (in milli seconds) before the send operation should be aborted
+	 * @return the timeout
+	 */
+	public long getTimeout()
+	{
+		return m_timeout;
+	}
+
+	/**
+	 * Set the send timeout, after the Email sender is connected to the SMTP server this is the maximum amount of time
+	 * it will wait before aborting the send operation
+	 * @param timeout the timeout in milli seconds. The default is 60 seconds (60000 milli seconds)
+	 */
+	public void setTimeout(long timeout)
+	{
+		m_timeout = timeout;
+	}
+
+	/**
+	 * Get the SMTP host
+	 * @return the host to send to
+	 */
+	public String getHost()
+	{
+		return m_host;
+	}
+
+	/**
+	 * Set the smtp server host or IP address
+	 * @param host the host
+	 */
+	public void setHost(String host)
+	{
+		m_host = host;
+	}
+
+	/**
+	 * Get the smtp server post
+	 * @return the port
+	 */
+	public int getPort()
+	{
+		return m_port;
+	}
+
+	/**
+	 * Set the SMTP servers port
+	 * @param port the port
+	 */
+	public void setPort(int port)
+	{
+		m_port = port;
+	}
+
+	/**
+	 * Get the message encryption
+	 * @return the selected encryption
+	 * @see Encryption
+	 */
+	public Encryption getEncryption()
+	{
+		return m_encryption;
+	}
+
+	/**
+	 * Sets the message encryption
+	 * @param encryption the encryption
+	 */
+	public void setEncryption(Encryption encryption)
+	{
+		m_encryption = encryption;
+	}
+
+	/**
+	 * Sets the to address field, if multiple email addresses are given they must be separated by space
+	 * @param to the to addresses
+	 */
+	public void setToAddress(String to)
+	{
+		m_toAddress = to;
+	}
+
+	/**
+	 * Get the To address configured
+	 * @return the to address, or multiple separated by space
+	 */
+	public String getToAddress()
+	{
+		return m_toAddress;
+	}
+
+	public void clearSensitiveInfo()
+	{
+	 	m_password = USE_TRANSITIVE_PASSWORD;
+	}
+
+	public IEmailSender doClone()
+	{
+		GenericEmailSender sender = new GenericEmailSender();
+		sender.setSubjectPrefix(getSubjectPrefix());
+		sender.setEncryption(getEncryption());
+		sender.setHost(getHost());
+		sender.setPassword(getPassword());
+		sender.setPort(getPort());
+		sender.setTimeout(getTimeout());
+		sender.setToAddress(getToAddress());
+		sender.setUserName(getUserName());
+		return sender;
+	}
+
+	public String getSubjectPrefix()
+	{
+		return m_subjectPrefix;
+	}
+
+	public void setSubjectPrefix(String subjectPrefix)
+	{
+		m_subjectPrefix = subjectPrefix;
+	}
+
+	public String getDisplayName() {
+		return "Generic SMTP";
+	}
+
+	public EditorPanel getEditor()
+	{
+		return new EmailSenderEditor(this, new EmailSenderEditor.EditorConfiguration(true, true, true));
+	}
+
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == GenericEmailSender.class;
+	}
+
+	@Override
+	public String toString()
+	{
+		return "GenericEmailSender{" +
+				"m_toAddress='" + m_toAddress + '\'' +
+				", m_userName='" + m_userName + '\'' +
+				", m_host='" + m_host + '\'' +
+				", m_port=" + m_port +
+				", m_encryption=" + m_encryption +
+				'}';
+	}
+}
Index: src/games/strategy/triplea/pbem/AxisAndAlliesDotOrgPBEMMessenger.java
===================================================================
--- src/games/strategy/triplea/pbem/AxisAndAlliesDotOrgPBEMMessenger.java	(revision 3313)
+++ src/games/strategy/triplea/pbem/AxisAndAlliesForumPoster.java	(revision )
@@ -11,40 +11,54 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
-/*
- * AxisAndAlliesDotOrgPBEMMessenger.java
- * 
- * 
- * Created on November 21, 2006, 8:56 PM
- */
 package games.strategy.triplea.pbem;
 
-import games.strategy.engine.pbem.IPBEMSaveGameMessenger;
-import games.strategy.engine.pbem.IPBEMTurnSummaryMessenger;
+import games.strategy.engine.framework.startup.launcher.ILauncher;
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.ForumPosterEditor;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+import games.strategy.engine.pbem.IForumPoster;
 import games.strategy.net.BrowserControl;
 import games.strategy.net.MultiPartFormOutputStream;
 
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
+import java.io.*;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
+ * Class is serialized and stored the users local cache. Be careful when adding
  * @author Tony Clayton
  * @version 1.0
  */
-public class AxisAndAlliesDotOrgPBEMMessenger implements IPBEMTurnSummaryMessenger, IPBEMSaveGameMessenger
+public class AxisAndAlliesForumPoster implements IForumPoster
 {
+	//-----------------------------------------------------------------------
+	// Class fields
+	//-----------------------------------------------------------------------
+	private static final Logger s_logger = Logger.getLogger(ILauncher.class.getName());
 	private static final int m_loginDelayInSeconds = 16;
 	private static final String m_host = "www.axisandallies.org";
-	private transient String m_username = null;
-	private transient String m_password = null;
+	private static final long serialVersionUID = 7957926213311732949L;
+	private static final String USE_TRANSITIVE_PASSWORD = "d0a11f0f-96d3-4303-8875-4965aefb2ce4";
+
+	//-----------------------------------------------------------------------
+	// instance fields
+	//-----------------------------------------------------------------------
+	private String m_username = null;
+	private String m_password = null;
+	private transient String m_transPassword;
+	private String m_gameId = null;
+	private boolean m_includeSaveGame = true;
+
+
+	//-----------------------------------------------------------------------
+	// Transient fields
+	//-----------------------------------------------------------------------
 	private transient String m_cookies = null;
 	private transient String m_referer = null;
 	private transient String m_sesc = null;
@@ -53,49 +67,64 @@
 	private transient String m_attachId = null;
 	private transient String m_seqNum = null;
 	private transient String m_screenshotRef = null;
-	private transient String m_saveGameRef = null;
 	private transient String m_turnSummaryRef = null;
-	private transient String m_saveGameFileName = null;
-	private transient InputStream m_saveGameFileIn = null;
-	private String m_gameId = null;
-	
-	public AxisAndAlliesDotOrgPBEMMessenger()
+	private transient File m_saveGameFile = null;
+	private transient String m_saveGameRef = null;
+	//-----------------------------------------------------------------------
+	// constructors
+	//-----------------------------------------------------------------------
+	public AxisAndAlliesForumPoster()
 	{
 	}
-	
+
-	public String getName()
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	public EditorPanel getEditor()
 	{
-		return "www.AxisAndAllies.org";
+		return new ForumPosterEditor(this);
 	}
-	
+
-	public boolean getNeedsUsername()
+	public boolean sameType(IBean other)
 	{
-		return true;
+		return other.getClass() == AxisAndAlliesForumPoster.class;
 	}
-	
+
-	public boolean getNeedsPassword()
+	public String getDisplayName()
 	{
-		return true;
+		return "www.AxisAndAllies.org";
 	}
 	
+
 	public boolean getCanViewPosted()
 	{
 		return true;
 	}
-	
+
+	public String getScreenshotRef()
+	{
+		return m_screenshotRef;
+	}
+
 	public void viewPosted()
 	{
 		final String url = "http://" + m_host + "/forums/index.php?topic=" + m_gameId + ".new#new";
 		BrowserControl.displayURL(url);
 	}
-	
+
-	public void setGameId(final String gameId)
+	public void clearSensitiveInfo()
 	{
-		m_gameId = gameId;
+	 	m_password = USE_TRANSITIVE_PASSWORD;
 	}
-	
+
-	public String getGameId()
+	public void setForumId(final String forumId)
 	{
+		m_gameId = forumId;
+	}
+	
+	public String getForumId()
+	{
 		return m_gameId;
 	}
 	
@@ -112,10 +141,14 @@
 	public void setPassword(final String password)
 	{
 		m_password = password;
+		m_transPassword = password;
 	}
 	
 	public String getPassword()
 	{
+		if (USE_TRANSITIVE_PASSWORD.equals(m_password)) {
+			return m_transPassword;
+		}
 		return m_password;
 	}
 	
@@ -177,7 +210,7 @@
 			urlConn.setRequestProperty("Cache-Control", "no-cache");
 			// send request to server
 			out = urlConn.getOutputStream();
-			out.write(new String("user=" + m_username + "&passwrd=" + m_password + "&cookielength=60").getBytes());
+			out.write(("user=" + m_username + "&passwrd=" + m_password + "&cookielength=60").getBytes());
 			out.flush();
 			out.close();
 			do
@@ -465,7 +498,7 @@
 		return "http://" + m_host + "/forums/index.php?action=dlattach;topic=" + m_gameId + ".0;attach=" + m_attachId;
 	}
 	
-	public boolean postTurnSummary(final String summary, final String screenshotRef, final String saveGameRef)
+	public boolean postTurnSummary(final String summary)
 	{
 		URL url = null;
 		URLConnection urlConn = null;
@@ -475,10 +508,7 @@
 		String saveGameSummary = "";
 		String finalSummary = "";
 		m_turnSummaryRef = null;
-		m_screenshotRef = screenshotRef;
-		// if we aren't the save game poster then set it
-		if (m_saveGameFileIn == null)
-			m_saveGameRef = saveGameRef;
+
 		// first, login if necessary
 		if (m_cookies == null && !postLogin())
 			return false;
@@ -500,11 +530,10 @@
 		forumSummary = "\n\nA&A Forum : " + getNextReplyUrl();
 		if (m_screenshotRef != null)
 			screenshotSummary = "\nScreenshot: " + m_screenshotRef;
-		if (m_saveGameRef != null)
-			saveGameSummary = "\nSave Game : " + m_saveGameRef;
+
 		finalSummary = summary + forumSummary + screenshotSummary + saveGameSummary;
 		int code;
-		BufferedReader in;
+		BufferedReader in = null;
 		String line;
 		Pattern p1;
 		Pattern p2;
@@ -535,59 +564,69 @@
 			out.writeField("goback", "1");
 			out.writeField("ns", "NS");
 			// empty attachment
-			if (m_saveGameFileIn != null)
+			if (m_saveGameFile != null)
 			{
-				out.writeFile("attachment[]", "application/octet-stream", m_saveGameFileName, m_saveGameFileIn);
+				FileInputStream fin = null;
+
+				try
+				{
+					fin = new FileInputStream(m_saveGameFile);
+					out.writeFile("attachment[]", "application/octet-stream", m_saveGameFile.getName(), fin);
-				out.writeField("attachmentPreview", "");
+					out.writeField("attachmentPreview", "");
+				} finally
+				{
+					if (fin != null)
+					{
+						try
+						{
+							fin.close();
+						} catch (IOException e)
+						{
+							// ignore
-			}
+						}
+					}
+				}
+			}
 			out.writeField("post", "Post");
 			out.writeField("num_replies", m_numReplies);
 			out.writeField("sc", m_sesc);
 			out.writeField("seqnum", m_seqNum);
 			out.close();
 			code = ((HttpURLConnection) urlConn).getResponseCode();
+			// 200 OK for error, redirects 301/302 if successful
 			if (code == 200)
 			{
-				// reporting an error
+
 				in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
-				line = "";
-				p1 = Pattern.compile(".*?<b>(The following error or errors occurred .*?)</b>.*");
-				p2 = Pattern.compile(".*?<td>(An Error Has Occurred!)</td>.*");
-				p3 = Pattern.compile(".*?session timed out.*"); // found after p1
+				StringBuilder sb = new StringBuilder();
+				String responseBody; // read the whole response for easier debugging
+				try
+				{
-				while ((line = in.readLine()) != null)
-				{
+					while ((line = in.readLine()) != null)
+					{
-					final Matcher m1 = p1.matcher(line);
-					if (m1.matches())
-					{
-						line = in.readLine(); // div start
-						line = in.readLine().trim(); // message
-						final Matcher m3 = p3.matcher(line);
-						if (m3.matches())
-						{
-							// login again and repost
-							m_cookies = null;
-							postTurnSummary(summary, screenshotRef, saveGameRef);
+						sb.append(line);
-						}
+					}
-						m_turnSummaryRef = m1.group(1) + "\n" + line;
-						in.close();
-						return false;
-					}
-					final Matcher m2 = p2.matcher(line);
-					if (m2.matches())
+					responseBody = sb.toString();
+
+
+				} finally
-					{
+				{
-						line = in.readLine(); // </tr>
-						line = in.readLine(); // <tr..>
-						line = in.readLine(); // <td..>
-						line = in.readLine(); // message
-						m_turnSummaryRef = m2.group(1) + "\n" + line.trim();
-						in.close();
+					in.close();
+				}
+
+				// The forum may also report <div...id="error_list">Sorry, you are not allowed to post links.</div>
+				Pattern p4 = Pattern.compile(".*?id=\"error_list[^>]*>\\s+([^<]*)\\s+<.*");
+				Matcher matcher = p4.matcher(responseBody);
+				if (matcher.matches()) {
+					m_turnSummaryRef = "The site gave an error: '" + matcher.group(1) + "'";
-						return false;
-					}
+					return false;
+				}
-					m_turnSummaryRef = "An unknown error occurred while POSTing.";
-					in.close();
+
+				s_logger.warning("Unknown error html: " + responseBody);
+				m_turnSummaryRef = "Unknown error, if this problem persists, post an error to the TripleA dev forum";
-					return false;
+				return false;
+
-				}
+			}
-			}
 			if (code != 301 && code != 302)
 			{
 				// http error
@@ -599,13 +638,26 @@
 			// now, go back to forum
 			if (!goToForum())
 				return false;
+
 		} catch (final Exception e)
 		{
-			e.printStackTrace();
+
+			s_logger.severe(e.getMessage());
+			m_turnSummaryRef = "Unknown error, message written to debug log";
 			return false;
+		} finally {
+			if (in != null) {
+				try
+				{
+					in.close();
+				} catch (IOException e)
+				{
+					// ignore
-		}
+				}
+			}
+		}
 		m_turnSummaryRef = getNewReplyUrl();
-		if (m_saveGameFileIn != null)
+		if (m_saveGameFile != null)
 		{
 			if (m_attachId == null)
 			{
@@ -621,24 +673,36 @@
 	{
 		return m_turnSummaryRef;
 	}
-	
+
-	public boolean postSaveGame(final String filename, final InputStream fileIn)
+	public boolean getIncludeSaveGame()
 	{
-		// not actually posting, just holding the reference
-		m_saveGameRef = null;
-		m_saveGameFileName = filename;
-		m_saveGameFileIn = fileIn;
-		return true;
+		return m_includeSaveGame;
 	}
-	
+
-	public String getSaveGameRef()
+	public void setIncludeSaveGame(boolean include)
 	{
-		return m_saveGameRef;
+	 	m_includeSaveGame = include;
 	}
-	
+
-	@Override
-	public String toString()
+	public void addSaveGame(File saveGameFile, String fileName)
 	{
-		return getName();
+		m_saveGameFile = saveGameFile;
 	}
+
+	public IForumPoster doClone()
+	{
+		AxisAndAlliesForumPoster clone = new AxisAndAlliesForumPoster();
+		clone.setForumId(getForumId());
+		clone.setIncludeSaveGame(getIncludeSaveGame());
+		clone.setPassword(getPassword());
+		clone.setUsername(getUsername());
+		return clone;
-}
+	}
+
+	public boolean supportsSaveGame()
+	{
+		return true;
+	}
+
+
+}
Index: src/games/strategy/engine/framework/startup/ui/editors/DiceServerEditor.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/DiceServerEditor.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/DiceServerEditor.java	(revision )
@@ -0,0 +1,191 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import games.strategy.engine.framework.startup.ui.editors.validators.EmailValidator;
+import games.strategy.engine.random.IRemoteDiceServer;
+import games.strategy.engine.random.PBEMDiceRoller;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * An class for editing a Dice Server
+ *
+ * @author Klaus Groenbaek
+ */
+public class DiceServerEditor extends EditorPanel
+{
+	//-----------------------------------------------------------------------
+	// instance fields 
+	//-----------------------------------------------------------------------
+
+	private final JButton m_testDiceyButton = new JButton("Test Server");
+	private final JTextField m_toAddress = new JTextField();
+	private final JTextField m_ccAddress = new JTextField();
+	private final JTextField m_gameId = new JTextField();
+
+	private JLabel m_toLabel = new JLabel("To:");
+	private JLabel m_ccLabel = new JLabel("Cc:");
+	private JLabel m_gameIdLabel = new JLabel("Game ID:");
+	private IRemoteDiceServer m_bean;
+	//-----------------------------------------------------------------------
+	// constructors 
+	//-----------------------------------------------------------------------
+
+	public DiceServerEditor(IRemoteDiceServer diceServer)
+	{
+		m_bean = diceServer;
+
+		int bottomSpace = 1;
+		int labelSpace = 2;
+		int row = 0;
+		JTextArea instructionText = new JTextArea();
+		instructionText.setLineWrap(true);
+		instructionText.setWrapStyleWord(true);
+		add(instructionText, new GridBagConstraints(0, row, 3, 1, 1.0d, 0.2d, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+		String text = m_bean.getInfoText();
+		if (text == null)
+		{
+			text = "The author of the properties file should set infotext property";
+		}
+		instructionText.setText(text);
+		instructionText.setBackground(getBackground());
+
+		row++;
+		if (m_bean.sendsEmail())
+		{
+
+			add(m_toLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+			add(m_toAddress, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+			m_toAddress.setText(m_bean.getToAddress());
+			row++;
+
+			add(m_ccLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+			add(m_ccAddress, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+			m_ccAddress.setText(m_bean.getCcAddress());
+			row++;
+		}
+		if (m_bean.supportsGameId())
+		{
+			add(m_gameIdLabel, new GridBagConstraints(0, row, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, labelSpace), 0, 0));
+			add(m_gameId, new GridBagConstraints(1, row, 2, 1, 1.0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, bottomSpace, 0), 0, 0));
+			m_gameId.setText(m_bean.getGameId());
+			row++;
+		}
+		add(m_testDiceyButton, new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, bottomSpace, 0), 0, 0));
+
+		setupListeners();
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	/**
+	 * Configures the listeners for the gui components
+	 */
+	private void setupListeners()
+	{
+
+		m_testDiceyButton.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(ActionEvent e)
+			{
+				final PBEMDiceRoller random = new PBEMDiceRoller(getDiceServer(), null);
+				random.test();
+			}
+		});
+
+		final DocumentListener docListener = new NotifyListenersDocumentListener();
+		m_toAddress.getDocument().addDocumentListener(docListener);
+		m_ccAddress.getDocument().addDocumentListener(docListener);
+	}
+
+	/**
+	 * Fires a property change so anyone listening to this editor will be notified
+	 */
+	private void notifyListeners()
+	{
+		firePropertyChange(EDITOR_CHANGE, null, null);
+	}
+
+
+	/**
+	 * call to check if the dice server configuration is valid
+	 *
+	 * @return true if valid
+	 */
+	private boolean validateInput()
+	{
+		boolean toValid = true;
+		boolean ccValid = true;
+
+		if (getDiceServer().sendsEmail())
+		{
+			toValid = validateTextField(m_toAddress, m_toLabel, new EmailValidator(false));
+			ccValid = validateTextField(m_ccAddress, m_ccLabel, new EmailValidator(true));
+		}
+
+		boolean allValid = toValid && ccValid;
+		m_testDiceyButton.setEnabled(allValid);
+		return allValid;
+	}
+
+	public boolean isInputValid()
+	{
+		return validateInput();
+	}
+
+	@Override
+	public IBean getBean()
+	{
+		return getDiceServer();
+	}
+
+
+	/**
+	 * Returns the currently configured dice server
+	 * @return the dice server
+	 */
+	public IRemoteDiceServer getDiceServer()
+	{
+		if (m_bean.sendsEmail())
+		{
+			m_bean.setCcAddress(m_ccAddress.getText());
+			m_bean.setToAddress(m_toAddress.getText());
+		}
+		if (m_bean.supportsGameId())
+		{
+			m_bean.setGameId(m_gameId.getText());
+		}
+		return m_bean;
+	}
+
+	//-----------------------------------------------------------------------
+	// inner classes
+	//-----------------------------------------------------------------------
+
+	/**
+	 * A document listener that notify our listeners when documents change
+	 */
+	private class NotifyListenersDocumentListener implements DocumentListener
+	{
+		public void changedUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void insertUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+
+		public void removeUpdate(final DocumentEvent e)
+		{
+			notifyListeners();
+		}
+	}
+}
Index: src/games/strategy/triplea/TripleA.java
===================================================================
--- src/games/strategy/triplea/TripleA.java	(revision 3372)
+++ src/games/strategy/triplea/TripleA.java	(revision )
@@ -19,22 +19,15 @@
  */
 package games.strategy.triplea;
 
-import games.strategy.engine.data.GameData;
-import games.strategy.engine.data.IUnitFactory;
-import games.strategy.engine.data.PlayerID;
-import games.strategy.engine.data.Unit;
-import games.strategy.engine.data.UnitType;
+import games.strategy.engine.data.*;
 import games.strategy.engine.framework.IGame;
 import games.strategy.engine.framework.IGameLoader;
 import games.strategy.engine.framework.ServerGame;
 import games.strategy.engine.gamePlayer.IGamePlayer;
 import games.strategy.engine.message.IChannelSubscribor;
 import games.strategy.engine.message.IRemote;
-import games.strategy.engine.pbem.AllYouCanUploadDotComPBEMMessenger;
-import games.strategy.engine.pbem.IPBEMMessenger;
 import games.strategy.triplea.Dynamix_AI.Dynamix_AI;
 import games.strategy.triplea.delegate.EditDelegate;
-import games.strategy.triplea.pbem.AxisAndAlliesDotOrgPBEMMessenger;
 import games.strategy.triplea.player.ITripleaPlayer;
 import games.strategy.triplea.sound.SoundPath;
 import games.strategy.triplea.strongAI.StrongAI;
@@ -43,16 +36,14 @@
 import games.strategy.triplea.ui.display.TripleaDisplay;
 import games.strategy.triplea.weakAI.WeakAI;
 
-import java.awt.Frame;
+import javax.swing.*;
+import java.awt.*;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
-import javax.swing.SwingUtilities;
-
 /**
  * @author Sean Bridges
  * @version 1.0
@@ -69,9 +60,6 @@
 	private transient TripleaDisplay m_display;
 	private transient IGame m_game;
 	
-	/**
-	 * @see IGameLoader.createPlayers(playerNames)
-	 */
 	public Set<IGamePlayer> createPlayers(final Map<String, String> playerNames)
 	{
 		final Set<IGamePlayer> players = new HashSet<IGamePlayer>();
@@ -205,11 +193,6 @@
 		return new String[] { HUMAN_PLAYER_TYPE, WEAK_COMPUTER_PLAYER_TYPE, STRONG_COMPUTER_PLAYER_TYPE, DYNAMIX_COMPUTER_PLAYER_TYPE };
 	}
 	
-	public IPBEMMessenger[] getPBEMMessengers()
-	{
-		return new IPBEMMessenger[] { new AxisAndAlliesDotOrgPBEMMessenger(), new AllYouCanUploadDotComPBEMMessenger() };
-	}
-	
 	/*
 	 * @see games.strategy.engine.framework.IGameLoader#getDisplayType()
 	 */
Index: src/games/strategy/engine/pbem/IPBEMSaveGameMessenger.java
===================================================================
--- src/games/strategy/engine/pbem/IPBEMSaveGameMessenger.java	(revision 3313)
+++ src/games/strategy/engine/pbem/IPBEMSaveGameMessenger.java	(revision 3313)
@@ -1,24 +0,0 @@
-/*
- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-package games.strategy.engine.pbem;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface IPBEMSaveGameMessenger extends IPBEMMessenger
-{
-	public boolean postSaveGame(String fname, InputStream is) throws IOException;
-	
-	public String getSaveGameRef();
-}
Index: src/games/strategy/engine/pbem/GmailEmailSender.java
===================================================================
--- src/games/strategy/engine/pbem/GmailEmailSender.java	(revision )
+++ src/games/strategy/engine/pbem/GmailEmailSender.java	(revision )
@@ -0,0 +1,57 @@
+package games.strategy.engine.pbem;
+
+import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
+import games.strategy.engine.framework.startup.ui.editors.EmailSenderEditor;
+import games.strategy.engine.framework.startup.ui.editors.IBean;
+
+/**
+ * @author Klaus Groenbaek
+ */
+public class GmailEmailSender extends GenericEmailSender
+{
+	private static final long serialVersionUID = 3511375113962472063L;
+
+	//-----------------------------------------------------------------------
+	// constructors 
+	//-----------------------------------------------------------------------
+
+	public GmailEmailSender()
+	{
+		setHost("smtp.gmail.com");
+		setPort(587);
+		setEncryption(Encryption.TLS);
+	}
+
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+
+	@Override
+	public EditorPanel getEditor()
+	{
+		return new EmailSenderEditor(this, new EmailSenderEditor.EditorConfiguration());
+	}
+
+	@Override
+	public String getDisplayName()
+	{
+		return "Gmail";
+	}
+
+	@Override
+	public boolean sameType(IBean other)
+	{
+		return other.getClass() == GmailEmailSender.class;
+	}
+
+	@Override
+	public IEmailSender doClone()
+	{
+		GenericEmailSender sender = new GmailEmailSender();
+		sender.setSubjectPrefix(getSubjectPrefix());
+		sender.setPassword(getPassword());
+		sender.setToAddress(getToAddress());
+		sender.setUserName(getUserName());
+		return sender;
+	}
+}
Index: src/games/strategy/engine/framework/startup/ui/editors/IBean.java
===================================================================
--- src/games/strategy/engine/framework/startup/ui/editors/IBean.java	(revision )
+++ src/games/strategy/engine/framework/startup/ui/editors/IBean.java	(revision )
@@ -0,0 +1,31 @@
+package games.strategy.engine.framework.startup.ui.editors;
+
+import java.io.Serializable;
+
+/**
+ * An interface specifying that this component is a bean that can provide an editor
+ * Beans must have a default constructor
+ * @author Klaus Groenbaek
+ */
+public interface IBean extends Serializable
+{
+	//-----------------------------------------------------------------------
+	// instance methods
+	//-----------------------------------------------------------------------
+	public String getDisplayName();
+
+	/**
+	 * Returns the editor for this IBean
+	 * @return the editor
+	 */
+	public EditorPanel getEditor();
+
+	/**
+	 * Method to check of two beans are of the same type,
+	 * this is used in the select and view to find an an bean in the list
+	 * @param other the bean to check against
+	 * @return true if they are the same type
+	 */
+	public boolean sameType(IBean other);
+
+}

