diff FrmMainWindow.cs @ 0:1bb28f84d567

Initial commit of WarFoundry code
author IBBoard <dev@ibboard.co.uk>
date Fri, 19 Dec 2008 15:57:51 +0000
parents
children 65279b85446f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FrmMainWindow.cs	Fri Dec 19 15:57:51 2008 +0000
@@ -0,0 +1,925 @@
+// FrmMainWindow.cs
+//
+//  Copyright (C) 2007 IBBoard
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License version 2.1 of the License as published by the Free
+// Software Foundation.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+//
+//
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Configuration;
+using Gtk;
+using IBBoard;
+using IBBoard.Commands;
+using IBBoard.IO;
+using IBBoard.Lang;
+using IBBoard.Logging;
+using IBBoard.CustomMath;
+using IBBoard.Log4Net;
+using IBBoard.WarFoundry.API;
+using IBBoard.WarFoundry.API.Factories;
+using IBBoard.WarFoundry.API.Factories.Xml;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.WarFoundry.API.Commands;
+using IBBoard.WarFoundry.API.Savers;
+using IBBoard.WarFoundry.API.Requirements;
+using IBBoard.WarFoundry.Widgets;
+using IBBoard.WarFoundry.Plugin.Rollcall;
+using IBBoard.Xml;
+using log4net;
+
+namespace IBBoard.WarFoundry
+{	
+	public partial class FrmMainWindow: Gtk.Window
+	{
+		private static readonly string AppTitle = "WarFoundry";
+		private const int CATEGORY_BUTTON_SEPARATOR_INDEX = 6;
+
+		private Preferences preferences;
+		private ILog logger = LogManager.GetLogger(typeof(FrmMainWindow));
+
+		private CommandStack commandStack;
+		private Type factoryType = typeof(WarFoundryXmlFactory);
+		private Dictionary<ToolButton, Category> categoryMap = new Dictionary<ToolButton, Category>();
+		private Dictionary<IBBoard.WarFoundry.API.Objects.Unit, UnitDisplayWidget> unitToWidgetMap = new Dictionary<IBBoard.WarFoundry.API.Objects.Unit,UnitDisplayWidget>();
+
+		private ObjectAddDelegate UnitAddedMethod;
+		private ObjectRemoveDelegate UnitRemovedMethod;
+		private DoubleValChangedDelegate PointsValueChangedMethod;
+		private FailedUnitRequirementDelegate FailedUnitRequirementMethod;
+		private StringValChangedDelegate UnitNameChangedMethod;
+		
+		private GameSystem system;
+		private string loadedArmyPath;
+		
+		private MenuToolButton undoMenuButton, redoMenuButton;
+		
+		public static void Main (string[] args)
+		{			
+			try
+			{
+				Application.Init();
+				FrmMainWindow win = new FrmMainWindow(args);
+				win.Show();
+				Application.Run();
+				LogManager.GetLogger(typeof(FrmMainWindow)).Debug("Application ended");
+			}
+			catch(Exception ex)
+			{
+				LogManager.GetLogger(typeof(FrmMainWindow)).Fatal(ex.Message + Environment.NewLine + ex.StackTrace);
+			}
+		}
+		
+		public FrmMainWindow() : this(new string[0])
+		{
+			//Do nothing extra
+		}
+			
+		public FrmMainWindow (string[] args): base (Gtk.WindowType.Toplevel)
+		{
+			logger.Info("Opening FrmMainWindow");
+			LogNotifierHandler.RegisterNotifierHandler();
+			Build ();
+			//Replace the undo/redo buttons with menu versions, which Monodevelop's GUI editor doesn't currently support
+			redoMenuButton = new MenuToolButton("gtk-redo");
+			redoMenuButton.Label = "Redo";
+			redoMenuButton.SetTooltip(new Tooltips(), "Redo", "");
+			redoMenuButton.Clicked+= redoTBButtonActivated;
+			toolbar.Insert(redoMenuButton, CATEGORY_BUTTON_SEPARATOR_INDEX);
+			undoMenuButton = new MenuToolButton("gtk-undo");
+			undoMenuButton.Label = "Undo";
+			undoMenuButton.SetTooltip(new Tooltips(), "Undo", "");
+			undoMenuButton.Clicked+= undoTBButtonActivated;
+			toolbar.Insert(undoMenuButton, CATEGORY_BUTTON_SEPARATOR_INDEX);
+			toolbar.Remove(toolbar.Children[CATEGORY_BUTTON_SEPARATOR_INDEX-1]);
+			toolbar.Remove(toolbar.Children[CATEGORY_BUTTON_SEPARATOR_INDEX-2]);
+			toolbar.ShowAll();
+			
+			Title = AppTitle;
+			TreeViewColumn mainColumn = new TreeViewColumn ();
+			mainColumn.Title = "Army Categories";
+			CellRendererText mainCell = new CellRendererText ();
+			mainColumn.PackStart (mainCell, true);
+			treeUnits.AppendColumn(mainColumn);
+			mainColumn.SetCellDataFunc(mainCell, new TreeCellDataFunc(RenderCategoryTreeObjectName));
+			treeUnits.Model = new TreeStore(typeof(WarFoundryObject));
+			logger.Debug("Loading preferences");
+			Preferences = new Preferences("WarFoundryGTK");
+			logger.Debug("Loading translations");
+			Translation.InitialiseTranslations(Constants.ExecutablePath, Preferences["language"].ToString());
+			logger.Debug("Initialising");
+			commandStack = new CommandStack();
+			commandStack.CommandStackUpdated+=new MethodInvoker(commandStack_CommandStackUpdated);
+			WarFoundryCore.GameSystemChanged+= new GameSystemChangedDelegate(OnGameSystemChanged);
+			WarFoundryCore.ArmyChanged+= new ArmyChangedDelegate(OnArmyChanged);
+			Destroyed+= new EventHandler(OnWindowDestroyed);
+			//TODO: Translate and subscribe to other events
+			UnitAddedMethod = new ObjectAddDelegate(OnUnitAdded);
+			UnitRemovedMethod = new ObjectRemoveDelegate(OnUnitRemoved);
+			PointsValueChangedMethod = new DoubleValChangedDelegate(OnPointsValueChanged);
+			FailedUnitRequirementMethod = new FailedUnitRequirementDelegate(OnFailedUnitRequirement);
+			UnitNameChangedMethod = new StringValChangedDelegate(OnUnitNameChanged);
+			logger.Debug("Initialising complete - trying to load default army or system");
+			
+			//FIXME: Temporary hack to add paths and factories
+			WarFoundryLoader.GetDefault().AddLoadDirectory(new DirectoryInfo(Constants.ExecutablePath + Constants.DirectoryString + "data"));
+			IWarFoundryFactory factory = WarFoundryFactoryFactory.GetFactoryFactory().GetFactory(typeof(WarFoundryXmlFactory));
+			
+			if (factory!=null && factory is WarFoundryXmlFactory)
+			{
+				WarFoundryLoader.GetDefault().RegisterFactory((WarFoundryXmlFactory)factory);
+			}
+			
+			factory = WarFoundryFactoryFactory.GetFactoryFactory().GetFactory(typeof(RollcallFactory));
+			
+			if (factory!=null && factory is RollcallFactory)
+			{
+				WarFoundryLoader.GetDefault().RegisterNonNativeFactory((INonNativeWarFoundryFactory)factory);
+			}
+								
+			if (args.Length == 1)
+			{
+				logger.Debug("Attempting to load from file");				
+				FileInfo file = new FileInfo(args[0]);
+				
+				try
+				{
+					//TODO: Try to load files
+					/*if (file.Extension.Equals("."+Factory.GetArmyFileExtension()))
+					{
+						WarFoundryCore.CurrentArmy = Factory.CreateArmyFromFile(file);
+						logger.InfoFormat("Loaded army from {0}", file.FullName);
+					}
+					else if (file.Extension.Equals("."+Factory.GetSystemFileExtension()))
+					{
+						WarFoundryCore.CurrentGameSystem = Factory.CreateGameSystemFromFile(file);
+						logger.InfoFormat("Loaded game system from {0}", file.FullName);
+					}*/
+				}
+				catch (InvalidFileException ex)
+				{
+					//TODO: show error dialog
+					logger.Error(ex);
+				}
+			}
+			else
+			{
+				string gameSystemID = Preferences.GetStringProperty("currSystem");
+
+				if (gameSystemID!=null && !"".Equals(gameSystemID))
+				{
+					logger.Debug("Attempting to load current game system from properties");
+					GameSystem sys = WarFoundryLoader.GetDefault().GetGameSystem(gameSystemID);
+					
+					if (sys!=null)
+					{
+						WarFoundryCore.CurrentGameSystem = sys;
+						logger.InfoFormat("Loaded game system {0} from properties", gameSystemID);
+					}
+				}
+			}
+		}
+		
+		private void RenderCategoryTreeObjectName(TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter)
+		{
+			object o = model.GetValue(iter, 0);
+			
+			if (o is ArmyCategory)
+			{
+				ArmyCategory c = (ArmyCategory)o;
+				string name = "";
+				
+				if (Preferences.GetBooleanProperty("ShowCatPercentage"))
+				{
+					name = Translation.GetTranslation("categoryTreeCatName", "{0} - {1}pts", c.Name, c.PointsTotal);
+				}
+				else
+				{
+					name = Translation.GetTranslation("categoryTreeCatNamePercentage", "{0} - {1}pts ({2}%)", c.Name, c.PointsTotal, (c.ParentArmy.PointsTotal > 0 ? Math.Round((c.PointsTotal / c.ParentArmy.PointsTotal) * 100) : 0));
+				}
+				
+				(cell as CellRendererText).Text = name;
+			}
+			else if (o is IBBoard.WarFoundry.API.Objects.Unit)
+			{
+				IBBoard.WarFoundry.API.Objects.Unit u = (IBBoard.WarFoundry.API.Objects.Unit)o;
+				string name = Translation.GetTranslation("categoryTreeCatName", "{0} - {1}pts", u.Name, u.PointsValue);
+				(cell as CellRendererText).Text = name;
+			}
+		}
+		
+		private void OnWindowDestroyed(object source, EventArgs args)
+		{
+			logger.Info("Exiting");
+			Application.Quit();
+		}
+		
+		private void OnUnitNameChanged(WarFoundryObject val, string oldValue, string newValue)
+		{
+			IBBoard.WarFoundry.API.Objects.Unit unit = (IBBoard.WarFoundry.API.Objects.Unit)val;
+			UnitDisplayWidget widget;
+			unitToWidgetMap.TryGetValue(unit, out widget);
+			
+			if (widget!=null)
+			{
+				unitsNotebook.SetTabLabelText(widget, newValue);
+			}
+		}
+		
+		private void OnUnitAdded(WarFoundryObject val)
+		{
+			IBBoard.WarFoundry.API.Objects.Unit unit = (IBBoard.WarFoundry.API.Objects.Unit)val;
+			unit.NameChanged+= UnitNameChangedMethod;
+			AddUnitToTree(unit);
+		}
+		
+		private void AddUnitToTree(IBBoard.WarFoundry.API.Objects.Unit unit)
+		{
+			TreeStore model = (TreeStore)treeUnits.Model;
+			TreeIter iter;
+			model.GetIterFirst(out iter);
+			
+			do
+			{
+				object obj = model.GetValue(iter, 0);
+				
+				if (obj is ArmyCategory)
+				{
+					ArmyCategory cat = (ArmyCategory)obj;
+					
+					if (cat.Equals(unit.Category))
+					{
+						model.AppendValues(iter, unit);
+						TreePath path = model.GetPath(iter);
+						treeUnits.ExpandToPath(path);
+					}
+				}
+			}
+			while (model.IterNext(ref iter));
+		}
+		
+		private void OnUnitRemoved(WarFoundryObject obj)
+		{
+			IBBoard.WarFoundry.API.Objects.Unit unit = (IBBoard.WarFoundry.API.Objects.Unit)obj;
+			unit.NameChanged-= UnitNameChangedMethod;
+			RemoveUnitFromTree(unit);
+			
+			//See if unit has a tab open and close it if it does
+		}
+		
+		private void RemoveUnitFromTree(IBBoard.WarFoundry.API.Objects.Unit unit)
+		{
+			TreeStore model = (TreeStore)treeUnits.Model;
+			TreeIter iter;
+			model.GetIterFirst(out iter);
+			bool removed = false;
+			
+			do
+			{
+				object obj = model.GetValue(iter, 0);
+				
+				if (obj is ArmyCategory)
+				{
+					ArmyCategory cat = (ArmyCategory)obj;
+					
+					if (unit.Category == null || cat.Equals(unit.Category))
+					{
+						TreeIter innerIter;
+						model.IterChildren(out innerIter, iter);
+						
+						do
+						{
+							object innerObj = model.GetValue(innerIter, 0);
+							
+							if (unit.Equals(innerObj))
+							{
+								model.Remove(ref innerIter);
+								removed = true;
+								break;
+							}
+						}
+						while (model.IterNext(ref innerIter));
+						
+						if (removed)
+						{
+							break;
+						}
+					}
+				}
+			}
+			while (model.IterNext(ref iter));
+		}
+		
+		private void OnPointsValueChanged(WarFoundryObject obj, double before, double after)
+		{
+			//Set points in panel
+		}
+		
+		private void OnFailedUnitRequirement(List<FailedUnitRequirement> failedRequirement)
+		{
+			//Show error message in panel
+		}
+		
+		public Preferences Preferences
+		{
+			get { return preferences; }
+			set { preferences = value; }		
+		}
+		
+		/*public AbstractNativeWarFoundryFactory Factory
+		{
+			get { return WarFoundryFactoryFactory.GetFactoryFactory().GetFactory(Constants.ExecutablePath, factoryType); }
+		}*/
+		
+		protected void OnDeleteEvent (object sender, DeleteEventArgs a)
+		{
+			Application.Quit ();
+			a.RetVal = true;
+		}
+
+		protected virtual void OnExitActivated(object sender, System.EventArgs e)
+		{
+			Application.Quit();
+		}
+
+		protected virtual void OnChangeGameSystemActivated(object sender, System.EventArgs e)
+		{
+			ChangeCurrentGameSystem();
+		}
+
+		protected virtual void OnCreateArmyActivated(object sender, System.EventArgs e)
+		{
+			CreateNewArmy();
+		}
+
+		protected virtual void OnReloadFilesActivated(object sender, System.EventArgs e)
+		{
+		}
+
+		protected virtual void OnSaveArmyAsActivated(object sender, System.EventArgs e)
+		{
+			SaveCurrentArmyAs();
+		}
+
+		protected virtual void OnCloseArmyActivated(object sender, System.EventArgs e)
+		{
+			CloseCurrentArmy();
+		}
+
+		protected virtual void OnOpenArmyActivated(object sender, System.EventArgs e)
+		{
+			OpenArmy();
+		}
+
+		protected virtual void OnSaveArmyActivated(object sender, System.EventArgs e)
+		{
+			SaveCurrentArmy();
+		}
+		
+		protected virtual void OnAddUnitActivated(object sender, System.EventArgs e)
+		{
+			if (sender is ToolButton)
+			{
+				Category cat = null;
+				categoryMap.TryGetValue((ToolButton)sender, out cat);
+				
+				if (cat!=null)
+				{
+					logger.DebugFormat("Show FrmNewUnit for {0}", cat.Name);
+					FrmNewUnit newUnit = new FrmNewUnit(WarFoundryCore.CurrentArmy.Race, cat, WarFoundryCore.CurrentArmy);
+					ResponseType response = (ResponseType)newUnit.Run();
+					newUnit.Hide();
+					
+					if (response==ResponseType.Ok)
+					{
+						CreateAndAddUnitCommand cmd = new CreateAndAddUnitCommand(newUnit.SelectedUnit, cat, WarFoundryCore.CurrentArmy);
+						commandStack.Execute(cmd);
+					}
+					
+					newUnit.Dispose();
+				}
+			}
+		}
+		
+		public CommandStack CommandStack
+		{
+			get { return commandStack; }
+		}
+		
+		private void SetAppTitle()
+		{
+			if (WarFoundryCore.CurrentArmy!=null)
+			{
+				Title = AppTitle + " - " + WarFoundryCore.CurrentGameSystem.Name + " - " + WarFoundryCore.CurrentArmy.Name;
+			}
+			else if (WarFoundryCore.CurrentGameSystem!=null)
+			{
+				Title = AppTitle + " - " + WarFoundryCore.CurrentGameSystem.Name;
+			}
+			else
+			{
+				Title = AppTitle;
+			}
+		}
+		
+		private void OnGameSystemChanged(GameSystem oldSys, GameSystem newSys)
+		{
+			system = newSys;
+			SetAppTitle();
+			miCreateArmy.Sensitive = system!=null;
+			newArmyButton.Sensitive = system!=null;
+			RemoveCategoryButtons();
+			
+			if (system!=null)
+			{
+				AddCategoryButtons(system.Categories);
+			}
+		}
+		
+		private void OnArmyChanged(Army oldArmy, Army newArmy)
+		{
+			loadedArmyPath = null;
+			SetAppTitle();
+			SetArmyTree(newArmy);
+						
+			if (oldArmy!=null)
+			{
+				oldArmy.UnitAdded-= UnitAddedMethod;
+				oldArmy.UnitRemoved-= UnitRemovedMethod;
+				oldArmy.PointsValueChanged-= PointsValueChangedMethod;
+				oldArmy.FailedRequirement-=FailedUnitRequirementMethod;				
+			}
+			
+			unitToWidgetMap.Clear();
+			
+			while (unitsNotebook.NPages > 0)
+			{
+				unitsNotebook.RemovePage(0);
+			}
+			
+			if (newArmy==null)
+			{
+				DisableCategoryButtons();
+			}
+			else
+			{
+				newArmy.UnitAdded+= UnitAddedMethod;
+				newArmy.UnitRemoved+= UnitRemovedMethod;
+				newArmy.PointsValueChanged+= PointsValueChangedMethod;
+				newArmy.FailedRequirement+=FailedUnitRequirementMethod;
+				//TODO: Clear all buttons
+				EnableCategoryButtons();
+
+				if (newArmy.Race.HasCategoryOverrides())
+				{
+					RemoveCategoryButtons();
+					AddCategoryButtons(newArmy.Race.Categories);
+				}
+			}
+			
+			miCloseArmy.Sensitive = newArmy!=null;
+			miSaveArmyAs.Sensitive = newArmy!=null;
+			//New army has no changes, so we can't save it
+			miSaveArmy.Sensitive = false;
+			saveArmyButton.Sensitive = false;
+
+			CommandStack.Reset();
+			SetPointsPanelText();
+		}
+		
+		private void SetArmyTree(Army army)
+		{
+			logger.Debug("Resetting tree");
+			TreeStore store = (TreeStore)treeUnits.Model;
+			store.Clear();
+			TreeIter iter;
+			
+			if (army!=null)
+			{
+				logger.Debug("Loading in categories to tree");
+								
+				foreach (ArmyCategory cat in army.Categories)
+				{
+					logger.DebugFormat("Append category {0}", cat.Name);
+					iter = store.AppendValues(cat);
+					
+					foreach (IBBoard.WarFoundry.API.Objects.Unit unit in cat.GetUnits())
+					{
+						store.AppendValues(iter, unit);
+					} 
+				}
+				
+				logger.Debug("Finished loading tree categories");
+			}
+		}
+		
+		private void DisableCategoryButtons()
+		{
+			SetCategoryButtonsSensitive(false);
+		}
+		
+		private void EnableCategoryButtons()
+		{
+			SetCategoryButtonsSensitive(true);
+		}
+		
+		private void SetCategoryButtonsSensitive(bool state)
+		{
+			int toolbarButtonCount = toolbar.Children.Length - 1;
+			logger.Debug("Last button index: "+toolbarButtonCount);
+			
+			for (int i = toolbarButtonCount; i > CATEGORY_BUTTON_SEPARATOR_INDEX; i--)
+			{
+				logger.DebugFormat("Setting button {0} state to {1}", i, state);
+				toolbar.Children[i].Sensitive = state;
+			}
+		}
+		
+		private void RemoveCategoryButtons()
+		{
+			int toolbarButtonCount = toolbar.Children.Length - 1;
+			
+			for (int i = toolbarButtonCount; i > CATEGORY_BUTTON_SEPARATOR_INDEX; i--)
+			{
+				toolbar.Remove(toolbar.Children[i]);
+			}
+			
+			categoryMap.Clear();
+		}
+		
+		private void AddCategoryButtons(Category[] cats)
+		{
+			if (cats!=null && cats.Length > 0)
+			{
+				logger.DebugFormat("Toolbar button count: {0}. Adding {1} categories.", toolbar.Children.Length, cats.Length);
+				
+				foreach (Category cat in cats)
+				{
+					ToolButton button = new ToolButton("gtk-add");
+					button.Label = cat.Name;
+					button.SetTooltip(new Tooltips(), "Add unit from "+cat.Name, "");
+					//TODO: See if we can associate data in some way, the same as we can with SWF. For now we just use the map.
+					categoryMap.Add(button, cat);
+					button.Clicked+= new System.EventHandler(OnAddUnitActivated);
+					toolbar.Insert(button, -1);
+				}
+			}
+			
+			toolbar.Children[CATEGORY_BUTTON_SEPARATOR_INDEX].Visible = cats!=null && cats.Length>0;
+			
+			toolbar.ShowAll();
+		}
+		
+		private void SetPointsPanelText()
+		{
+			//TODO: Set the points value in the status bar
+		}
+		
+		private void commandStack_CommandStackUpdated()
+		{
+			undoMenuButton.Sensitive = commandStack.CanUndo();
+			miUndo.Sensitive = undoMenuButton.Sensitive;
+			redoMenuButton.Sensitive = commandStack.CanRedo();
+			miRedo.Sensitive = redoMenuButton.Sensitive;
+			int redoLength = commandStack.RedoLength;
+			//TODO: Build menus for undo/redo and find way of adding tooltips
+			/*int maxRedo = Math.Min(10, redoLength);
+			MenuItem[] menuItems = null;
+		
+			if (redoLength > 0)
+			{
+				menuItems = new MenuItem[maxRedo];
+				Command com;
+				MenuItem mi;
+
+				for (int i = 0; i < maxRedo; i++)
+				{
+					com = commandStack.PeekRedoCommand(i+1);
+
+					if (com == null)
+					{
+						break; 
+					}
+
+					mi = new MenuItem(com.Description);
+					mi.Click+=new EventHandler(redoMenu_Click);
+					menuItems[i] = mi;
+				}
+			}
+
+			redoMenu.MenuItems.Clear();
+
+			if (menuItems!=null && menuItems[0]!=null)
+			{
+				bttnRedo.ToolTipText = menuItems[0].Text;
+				redoMenu.MenuItems.AddRange(menuItems);
+			}*/
+			//TODO: Put above code back when we have a dropdown version of the redo button
+			if (redoLength > 0)
+			{
+				//redoMenuButton.Tooltip = CommandStack.PeekRedoCommand().Description;
+			}
+
+			int undoLength = commandStack.UndoLength;
+			/*int maxUndo = Math.Min(10, undoLength);
+			MenuItem[] menuItemsUndo = null;
+			
+			if (undoLength > 0)
+			{
+				menuItemsUndo = new MenuItem[maxUndo];
+				Command com;
+				MenuItem mi;
+
+				for (int i = 0; i < maxUndo; i++)
+				{
+					com = commandStack.PeekUndoCommand(i+1);
+
+					if (com == null)
+					{
+						break; 
+					}
+
+					mi = new MenuItem(com.UndoDescription);
+					mi.Click+=new EventHandler(undoMenu_Click);
+					menuItemsUndo[i] = mi;
+				}
+			}
+
+			undoMenu.MenuItems.Clear();
+
+			if (menuItemsUndo!=null && menuItemsUndo[0]!=null)
+			{
+				bttnUndo.ToolTipText = menuItemsUndo[0].Text;
+				undoMenu.MenuItems.AddRange(menuItemsUndo);
+			}*/			
+			//TODO: Put above code back when we have a dropdown version of the undo button
+			if (undoLength > 0)
+			{
+				//undoMenuButton.Tooltip = CommandStack.PeekUndoCommand().UndoDescription;
+			}
+			
+			saveArmyButton.Sensitive = commandStack.IsDirty() && WarFoundryCore.CurrentArmy!=null && CanSave();
+			miSaveArmy.Sensitive = commandStack.IsDirty() && WarFoundryCore.CurrentArmy!=null && CanSave();
+		}	
+		
+		private bool CanSave()
+		{
+			return loadedArmyPath!=null && WarFoundryCore.CurrentArmy!=null && WarFoundrySaver.GetSaver()!=null;
+		}
+
+		private bool SaveCurrentArmyOrSaveAs()
+		{
+			if (CanSave())
+			{
+				return SaveCurrentArmy();
+			}
+			else 
+			{
+				return SaveCurrentArmyAs();
+			}
+		}
+		
+		private bool OpenArmy()
+		{
+			//TODO: Open dialog for file selection then open army
+			bool success = false;
+			loadedArmyPath = null;//TODO: Set loaded file path
+			return success;
+		}
+
+		private bool SaveCurrentArmy()
+		{
+			bool success = false;
+			
+			if (CanSave())
+			{
+				try
+				{
+					if (WarFoundrySaver.GetSaver().Save(WarFoundryCore.CurrentArmy, loadedArmyPath))
+					{
+						saveArmyButton.Sensitive = false;
+						miSaveArmy.Sensitive = false;
+						CommandStack.setCleanMark();
+						success = true;
+					}
+				}
+				catch (IOException ex)
+				{
+					logger.Error("Saving army failed", ex);
+					MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "An error occured while saving the army. Please check the logs for details on what failed");
+					md.Show();
+				}
+			}
+			
+			return success;
+		}
+
+		private bool SaveCurrentArmyAs()
+		{
+			/*if (saveArmyDialog.Filter == "")
+			{
+				string savePath = UserDataPath+Constants.DirectoryString+"armies"+Constants.DirectoryString;
+				
+				if (!Directory.Exists(savePath))
+				{
+					Directory.CreateDirectory(savePath);
+				}
+
+				saveArmyDialog.InitialDirectory = savePath;
+				saveArmyDialog.Filter = Translation.GetTranslation("armyFileFilter")+"|*.army";
+				saveArmyDialog.Title = Translation.GetTranslation("saveArmyDialog");
+			}
+
+			DialogResult dr = saveArmyDialog.ShowDialog(this);
+
+			if (dr == DialogResult.OK)
+			{
+				if (WarFoundrySaver.GetSaver().Save(WarFoundryCore.CurrentArmy, saveArmyDialog.FileName))
+				{
+					miSaveArmy.Enabled = false;
+					bttnSaveArmy.Enabled = false;
+					CommandStack.setCleanMark();
+					loadedArmyPath = saveArmyDialog.FileName;
+					return true;
+				}
+				else
+				{
+					MessageBox.Show(this, Translation.GetTranslation("SaveFailed"), Translation.GetTranslation("SaveFailedTitle"), MessageBoxButtons.OK, MessageBoxIcon.Error);
+					return false;
+				}
+			}
+			else
+			{
+				return false;
+			}*/
+			return false;
+		}
+		
+		private bool CloseCurrentArmy()
+		{
+			if (WarFoundryCore.CurrentArmy!=null)
+			{
+				bool canClose = false;
+
+				if (CommandStack.IsDirty())
+				{
+					MessageDialog dia  = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.YesNo | ButtonsType.Cancel, "The army \""+WarFoundryCore.CurrentArmy.Name+"\" has been modified.\r\nSave changes before closing army?");   
+					ResponseType dr = (ResponseType)dia.Run();
+
+					if (dr == ResponseType.Yes)
+					{
+						//They want to save so try to save it or prompt for save as
+						//If they cancel the save as then assume they don't want to close
+						canClose = SaveCurrentArmyOrSaveAs();
+					}
+					else if (dr == ResponseType.No)
+					{
+						//They don't care about their changes
+						canClose = true;
+					}
+					else
+					{
+						//Assume cancel or close with the X button
+						canClose = false;
+					}
+					
+					dia.Dispose();
+				}
+				else
+				{
+					//Nothing has changed so we can safely close
+					canClose = true;
+				}
+
+				if (canClose)
+				{
+					//do close
+					WarFoundryCore.CurrentArmy = null;
+					return true;
+				}
+				else
+				{
+					return false;
+				}
+			}
+			else
+			{
+				//pretend we succeeded
+				return true;
+			}
+		}
+
+		private void CreateNewArmy()
+		{
+			logger.Debug("Create new army");
+			FrmNewArmy newArmy = new FrmNewArmy(WarFoundryCore.CurrentGameSystem);
+			ResponseType type = (ResponseType)newArmy.Run();
+			newArmy.Hide();
+		
+			if (type == ResponseType.Ok)
+			{
+				if (CloseCurrentArmy())
+				{
+					WarFoundryCore.CurrentArmy = new Army(newArmy.SelectedRace, newArmy.ArmyName, newArmy.ArmySize);
+				}
+			}
+			else
+			{
+				logger.Debug("Create new army cancelled");
+			}
+			
+			newArmy.Destroy();
+		}
+		
+		private void ChangeCurrentGameSystem()
+		{				
+			logger.Debug("Changing game system");
+			FrmChangeGameSystem dialog = new FrmChangeGameSystem(this);
+			ResponseType type = (ResponseType)dialog.Run();
+			dialog.Hide();
+			
+			if (type == ResponseType.Ok)
+			{
+				WarFoundryCore.CurrentGameSystem = dialog.SelectedSystem;
+			}
+			else
+			{
+				logger.Debug("Game system change cancelled");
+			}
+			
+			dialog.Destroy();
+		}
+
+		protected virtual void undoTBButtonActivated (object sender, System.EventArgs e)
+		{
+			CommandStack.Undo();
+		}
+
+		protected virtual void redoTBButtonActivated (object sender, System.EventArgs e)
+		{
+			CommandStack.Redo();
+		}
+
+		protected virtual void saveTBButtonActivated (object sender, System.EventArgs e)
+		{
+			SaveCurrentArmy();
+		}
+
+		protected virtual void openTBButtonActivated (object sender, System.EventArgs e)
+		{
+			OpenArmy();
+		}
+
+		protected virtual void newTBButtonActivated (object sender, System.EventArgs e)
+		{
+			CreateNewArmy();
+		}
+
+		protected virtual void ArmyRowActivated (object o, Gtk.RowActivatedArgs args)
+		{
+			TreeModel model = treeUnits.Model;
+			TreeIter iter;
+			model.GetIter(out iter, args.Path);
+			object obj = model.GetValue(iter, 0);
+			
+			if (obj is IBBoard.WarFoundry.API.Objects.Unit)
+			{
+				IBBoard.WarFoundry.API.Objects.Unit unit = (IBBoard.WarFoundry.API.Objects.Unit)obj;
+				
+				UnitDisplayWidget widget;
+				unitToWidgetMap.TryGetValue(unit, out widget);
+				
+				if (widget!=null)
+				{
+					logger.DebugFormat("Selecting existing page for "+unit.Name);
+					unitsNotebook.Page = unitsNotebook.PageNum(widget);
+				}
+				else
+				{
+					widget = new UnitDisplayWidget(unit, CommandStack);
+					Label label = new Label(unit.Name);
+					logger.Debug("Adding page for "+unit.Name);
+					unitToWidgetMap[unit] = widget;
+					int pageNum = unitsNotebook.AppendPage(widget, label);
+					logger.Debug("Page added at index "+pageNum);
+					unitsNotebook.ShowAll();
+					unitsNotebook.Page = pageNum;
+				}
+			}
+		}
+	}
+}