// This file (FrmMainWindow.cs) is a part of the IBBoard.WarFoundry.GTK project and is copyright 2008, 2009 IBBoard. // // The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license. using System; using System.IO; using System.Collections.Generic; using System.Configuration; using Gtk; using IBBoard; using IBBoard.Commands; using IBBoard.GtkSharp; using IBBoard.IO; using IBBoard.Lang; using IBBoard.Logging; using IBBoard.CustomMath; 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.GTK.Widgets; using IBBoard.WarFoundry.Plugin.Rollcall; using IBBoard.Xml; using log4net; namespace IBBoard.WarFoundry.GTK { 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 Dictionary categoryMap = new Dictionary(); private Dictionary unitToWidgetMap = new Dictionary(); 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.GetType().Name+") "+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"); 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.TooltipText = "Redo"; redoMenuButton.Clicked+= redoTBButtonActivated; toolbar.Insert(redoMenuButton, CATEGORY_BUTTON_SEPARATOR_INDEX); undoMenuButton = new MenuToolButton("gtk-undo"); undoMenuButton.Label = "Undo"; undoMenuButton.TooltipText = "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"); try { Translation.InitialiseTranslations(Constants.ExecutablePath, Preferences["language"].ToString()); } catch (TranslationLoadException ex) { logger.Error(ex); MessageDialog dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, ex.Message); dialog.Title = "Translation loading failed"; dialog.Run(); dialog.Destroy(); } 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); //FIXME: Temporary hack to add paths and factories before we get preferences and plugins WarFoundryLoader.GetDefault().AddLoadDirectory(new DirectoryInfo(Constants.ExecutablePath + Constants.DirectoryString + "data")); WarFoundryLoader.GetDefault().RegisterFactory(WarFoundryXmlFactory.GetFactory()); WarFoundryLoader.GetDefault().RegisterNonNativeFactory(RollcallFactory.GetFactory()); WarFoundrySaver.SetFileSaver(new WarFoundryXmlSaver()); logger.Debug("Initialising complete - seeing if we can load default army or system"); if (args.Length == 1) { logger.Debug("Attempting to load from file"); FileInfo file = new FileInfo(args[0]); try { ICollection objects = WarFoundryLoader.GetDefault().LoadFile(file); if (objects.Count == 1) { List objectList = new List(); objectList.AddRange(objects); IWarFoundryObject loadedObject = objectList[0]; if (loadedObject is Army) { WarFoundryCore.CurrentArmy = (Army)loadedObject; logger.InfoFormat("Loaded army from {0}", file.FullName); } else if (loadedObject is GameSystem) { WarFoundryCore.CurrentGameSystem = (GameSystem)loadedObject; 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.SetTabLabel(widget, NotebookUtil.CreateNotebookTabLabelWithClose(unitsNotebook, 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 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, WarFoundryCore.CurrentArmy.GetCategory(cat)); 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.TooltipText = "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() { FileChooserDialog fileDialog = new FileChooserDialog("Open army", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); FileFilter filter = new FileFilter(); filter.AddPattern("*.army"); filter.Name = "WarFoundry Army files (*.army)"; fileDialog.AddFilter(filter); int response = fileDialog.Run(); string filePath = null; if (response == (int)ResponseType.Accept) { filePath = fileDialog.Filename; } fileDialog.Hide(); fileDialog.Dispose(); bool success = false; if (filePath != null) { FileInfo file = new FileInfo(filePath); Army army = WarFoundryLoader.GetDefault().LoadArmy(file); if (army != null) { logger.Debug("Loaded army " + army.ID); success = true; loadedArmyPath = filePath; WarFoundryCore.CurrentArmy = army; logger.Debug("Army loading complete"); } else { logger.ErrorFormat("Failed to load {0} as an army file", filePath); MessageDialog dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, file.Name + " could not be loaded.\n\nIf the file is an army file then please check your file loaders."); dialog.Title = "Failed to open army"; dialog.Run(); dialog.Hide(); dialog.Dispose(); } } else { logger.Debug("Army open requested but cancelled"); } return success; } private bool SaveCurrentArmy() { bool success = false; if (CanSave()) { success = SaveArmyToPath(WarFoundryCore.CurrentArmy, loadedArmyPath); } return success; } private bool SaveCurrentArmyAs() { FileChooserDialog fileDialog = new FileChooserDialog("Save file as", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "Save", ResponseType.Accept); FileFilter filter = new FileFilter(); filter.AddPattern("*.army"); filter.Name = "WarFoundry Army files (*.army)"; fileDialog.AddFilter(filter); int response = fileDialog.Run(); string filePath = null; if (response == (int)ResponseType.Accept) { filePath = fileDialog.Filename; } fileDialog.Hide(); fileDialog.Dispose(); return SaveArmyToPath(WarFoundryCore.CurrentArmy, filePath); } private bool SaveArmyToPath(Army army, string filePath) { bool success = false; if (filePath!=null) { if (WarFoundrySaver.GetSaver().Save(WarFoundryCore.CurrentArmy, filePath)) { miSaveArmy.Sensitive = false; saveArmyButton.Sensitive = false; CommandStack.setCleanMark(); loadedArmyPath = filePath; success = true; } else { MessageDialog dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "Failed to save file to "+filePath); dialog.Title = "Army save failed"; dialog.Run(); dialog.Hide(); dialog.Dispose(); } } //else user cancelled return success; } private bool CloseCurrentArmy() { if (WarFoundryCore.CurrentArmy!=null) { bool canClose = false; if (CommandStack.IsDirty()) { MessageDialog dia = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.YesNo, "The army \""+WarFoundryCore.CurrentArmy.Name+"\" has been modified.\r\nSave changes before closing army?"); ResponseType dr = (ResponseType)dia.Run(); dia.Hide(); dia.Dispose(); 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; } } 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); logger.Debug("Adding page for "+unit.Name); unitToWidgetMap[unit] = widget; widget.Destroyed+= new EventHandler(UnitWidgetDestroyed); int pageNum = NotebookUtil.AddPageToNotebookWithCloseButton(unitsNotebook, widget, unit.Name); logger.Debug("Page added at index "+pageNum); unitsNotebook.ShowAll(); unitsNotebook.Page = pageNum; } } } private void UnitWidgetDestroyed(object sender, EventArgs e) { if (sender is UnitDisplayWidget) { unitToWidgetMap.Remove(((UnitDisplayWidget)sender).Unit); } } } }