view MainWindow.cs @ 26:55d4f16c982b

Fixes #250: QtSharp app doesn't always quit properly * Override Close event to dispose of file dialogs (open and save) * Remove unnecessary event handler hooked to close * Change app initialisation to match pattern used by Synapse * Create file dialogs without specifying "this" as parent - seems to be main fix, for some reason.
author IBBoard <dev@ibboard.co.uk>
date Sat, 31 Jul 2010 16:03:14 +0000
parents 9641addafffe
children 5ee15def17e7
line wrap: on
line source

// This file (MainWindow.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 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 Qyoto;
using log4net;
using IBBoard.Commands;
using IBBoard.IO;
using IBBoard.Lang;
using IBBoard.WarFoundry.API;
using IBBoard.WarFoundry.API.Exporters;
using IBBoard.WarFoundry.API.Factories;
using IBBoard.WarFoundry.API.Objects;
using IBBoard.WarFoundry.API.Savers;

namespace IBBoard.WarFoundry.GUI.QtSharp
{
	public class MainWindow : QMainWindow
	{
		private static readonly string AppTitle = "WarFoundry";
		
		private Ui_MainWindowLayout layout;
		private readonly ILog log = LogManager.GetLogger(typeof(MainWindow));
		private string loadedFilePath;
		private CommandStack commandStack;
		private QFileDialog saveArmyDialog;
		private QFileDialog openArmyDialog;
		private Preferences preferences;
		
		public MainWindow ()
		{
			layout = new Ui_MainWindowLayout();
			layout.SetupUi(this);
			WindowIcon = new QIcon("icons/App.png");
			Preferences = new Preferences("WarFoundryQt");
			
			try
			{
				Translation.InitialiseTranslations(Constants.ExecutablePath, Preferences["language"].ToString());
			}
			catch (TranslationLoadException ex)
			{
				log.Error(ex);
				QMessageBox.Critical(this, "Translation loading failed", ex.Message);
			}
			
			SetAppTitle();
			saveArmyDialog = new QFileDialog();
			saveArmyDialog.acceptMode = QFileDialog.AcceptMode.AcceptSave;
			saveArmyDialog.fileMode = QFileDialog.FileMode.AnyFile;
			saveArmyDialog.SetDirectory(new QDir(Environment.GetFolderPath(Environment.SpecialFolder.Personal)));
			openArmyDialog = new QFileDialog();
			openArmyDialog.acceptMode = QFileDialog.AcceptMode.AcceptOpen;
			openArmyDialog.fileMode = QFileDialog.FileMode.ExistingFile;
			openArmyDialog.SetNameFilter("*.army");
			openArmyDialog.SetDirectory(new QDir(Environment.GetFolderPath(Environment.SpecialFolder.Personal)));
			SetUpActionIcons();
			ConnectMenuActions();
			SetUpToolbar();
			layout.unitTabs.Clear();
			WarFoundryCore.ArmyChanged+= HandleWarFoundryCoreArmyChanged;
			CommandStack.CommandStackUpdated+= HandleCommandStackCommandStackUpdated;
		}
		
		protected override void CloseEvent(QCloseEvent arg1)
		{
			base.CloseEvent(arg1);
			saveArmyDialog.Dispose();
			openArmyDialog.Dispose();
		}
		
		private void SetUpActionIcons()
		{
			layout.actionCreateArmy.icon = new QIcon("icons/ui/filenew.png");
			layout.actionOpenArmy.icon = new QIcon("icons/ui/fileopen.png");
			layout.actionSaveArmy.icon = new QIcon("icons/ui/filesave.png");
			layout.actionSaveArmyAs.icon = new QIcon("icons/ui/filesaveas.png");
			layout.menuExportArmyAs.icon = new QIcon("icons/ui/export.png");
			layout.actionCloseArmy.icon = new QIcon("icons/ui/window-close.png");
			layout.actionExit.icon = new QIcon("icons/ui/exit.png");
			layout.actionUndo.icon = new QIcon("icons/ui/edit-undo.png");
			layout.actionRedo.icon = new QIcon("icons/ui/edit-redo.png");
			layout.actionAbout.icon = new QIcon("icons/ui/help-about.png");
		}
		
		private void ConnectMenuActions()
		{
			ConnectMenuAction(layout.actionCreateArmy, CreateNewArmy);
			ConnectMenuAction(layout.actionUndo, UndoAction);
			ConnectMenuAction(layout.actionRedo,RedoAction);
			ConnectMenuAction(layout.actionSaveArmyAs, DoSaveCurrentArmyAs);
			ConnectMenuAction(layout.actionSaveArmy, DoSaveCurrentArmy);
			ConnectMenuAction(layout.actionCloseArmy, DoCloseArmy);
			ConnectMenuAction(layout.actionOpenArmy, DoOpenArmy);
			ConnectMenuAction(layout.actionExportBasicHtml, DoExportBasicHTML);
		}
		
		private void ConnectMenuAction(QAction menuAction, SlotFunc method)
		{
			QObject.Connect(menuAction, SIGNAL("triggered()"), method);
		}
		
		private void CreateNewArmy()
		{
			if (CloseCurrentArmy())
			{
				NewArmyDialog dialog = new NewArmyDialog(this);
				int result = dialog.Exec ();
				
				if (result == (int)QDialog.DialogCode.Accepted)
				{
					try
					{
						CurrentArmy = new Army(dialog.GetSelectedRace(), dialog.GetArmyName(), dialog.GetArmySize());
					}
					catch (RequiredDataMissingException ex)
					{
						log.Error("Required data missing from race file", ex);
						QMessageBox.Critical(this, "Invalid race file data", "the race file for the requested data could not be loaded as it did not contain some required data");
					}
					catch (InvalidFileException ex)
					{
						log.Error("Race file was invalid", ex);
						QMessageBox.Critical(this, "invalid race file", ex.Message);
					}
				}
			}
		}
		
		private void SetUpToolbar()
		{
			layout.toolBar.AddAction(layout.actionCreateArmy);
			layout.toolBar.AddAction(layout.actionOpenArmy);
			layout.toolBar.AddAction(layout.actionSaveArmy);
			layout.toolBar.AddSeparator();
			layout.toolBar.AddAction(layout.actionUndo);
			layout.toolBar.AddAction(layout.actionRedo);
			layout.toolBar.AddSeparator();
		}		

		private void HandleWarFoundryCoreArmyChanged(Army oldValue, Army newValue)
		{
			CommandStack.Reset();
			loadedFilePath = null;
			layout.actionSaveArmy.Enabled = false;
			SetPointsPanelText();
			SetAppTitle();
		}

		private void SetPointsPanelText ()
		{
			//TODO: implement panel and points panel
		}

		
		private void SetAppTitle()
		{
			string str = AppTitle;

			if (CurrentGameSystem!=null)
			{
				str+= " - " + CurrentGameSystem.Name;
			
				if (CurrentArmy!=null)
				{
					str+= " - " + CurrentArmy.Name;
				}
			}

			this.WindowTitle = str;
		}
		
		public CommandStack CommandStack
		{
			get 
			{
				if (commandStack == null)
				{					
					commandStack = new CommandStack();
				}

				return commandStack; 
			}
		}

		private void HandleCommandStackCommandStackUpdated()
		{
			layout.actionUndo.Enabled = CommandStack.CanUndo();
			layout.actionRedo.Enabled = CommandStack.CanRedo();
			layout.actionSaveArmy.Enabled = CommandStack.IsDirty();
		}

		private void UndoAction()
		{
			if (commandStack.CanUndo())
			{
				commandStack.Undo();
			}
		}

		private void RedoAction()
		{
			if (commandStack.CanRedo())
			{
				commandStack.Redo();
			}
		}
		
		private void DoOpenArmy()
		{
			OpenArmy();
		}
		
		private bool OpenArmy()
		{
			log.Debug("Opening army");
			string newFilePath = PromptForFilePath (openArmyDialog);
			bool openedFile = false;

			if (newFilePath != null && CloseCurrentArmy())
			{
				try
				{
					log.DebugFormat("Opening {0}", newFilePath);
					CurrentArmy = WarFoundryLoader.GetDefault().LoadArmy(new FileInfo(newFilePath));
					loadedFilePath = newFilePath;
					openedFile = true;
				}
				catch (RequiredDataMissingException ex)
				{
					log.Error(ex);
					QMessageBox.Critical(this, Translation.GetTranslation("InvalidArmyFileBoxTitle", "invalid army file"), ex.Message);
				}
				catch (InvalidFileException ex)
				{
					log.Error(ex);
					QMessageBox.Critical(this, Translation.GetTranslation("InvalidArmyFileBoxTitle", "invalid army file"), ex.Message);
				}
			}
	
			return openedFile;
		}
		
		private void DoSaveCurrentArmy()
		{
			SaveCurrentArmy();
		}

		private bool SaveCurrentArmy()
		{
			log.Debug("Save current army");
			bool saved = false;

			string filePath = loadedFilePath;

			if (filePath == null)
			{
				filePath = PromptForArmyFilePath();
			}

			if (filePath != null)
			{
				saved = SaveCurrentArmyToFile(filePath);
			}

			return saved;
		}

		private void DoSaveCurrentArmyAs()
		{
			SaveCurrentArmyAs();
		}
		
		private bool SaveCurrentArmyAs()
		{
			log.Debug("Saving current army as a different file");
			bool saved = false;
			string filePath = PromptForArmyFilePath();

			if (filePath != null)
			{
				saved = SaveCurrentArmyToFile(filePath);
			}
			
			return saved;
		}

		private bool SaveCurrentArmyToFile(string filePath)
		{
			log.DebugFormat("Save to {0}", filePath);
			
			if (WarFoundrySaver.GetSaver().Save(CurrentArmy, filePath))
			{
				log.Debug("Army saved");
				loadedFilePath = filePath;
				layout.actionSaveArmy.Enabled = false;
				CommandStack.setCleanMark();
				return true;
			}
			else
			{
				log.Debug("Save failed");
				QMessageBox.Critical(this, "file save failed", "file save failed - check log for details");
				return false;
			}
		}

		private string PromptForArmyFilePath()
		{
			return PromptForFilePath (saveArmyDialog);
		}
		
		private string PromptForFilePath(QFileDialog qFileDialog)
		{
			int result = qFileDialog.Exec();
			string path = null;

			if (result == (int)QDialog.DialogCode.Accepted)
			{
				path = qFileDialog.SelectedFiles()[0];
			}
			
			return path;
		}		

		public GameSystem CurrentGameSystem
		{
			get { return WarFoundryCore.CurrentGameSystem; }
			set { WarFoundryCore.CurrentGameSystem = value; }
		}

		public Army CurrentArmy
		{
			get { return WarFoundryCore.CurrentArmy; }
			set { SetArmy(value); }
		}

		private void SetArmy(Army newArmy)
		{
			IgnoreArmy(CurrentArmy);
			CloseAllUnitWindows();

			if (newArmy == null)
			{
				SetNullArmyState();
			}
			else
			{
				WarFoundryCore.CurrentGameSystem = newArmy.GameSystem;
				ListenToArmy(newArmy);
				SetNonNullArmyState(newArmy);
			}
			
			WarFoundryCore.CurrentArmy = newArmy;
		}

		private void IgnoreArmy(Army oldArmy)
		{
			if (oldArmy != null)
			{
				oldArmy.UnitAdded -= UnitAddedMethod;
				oldArmy.UnitRemoved -= UnitRemovedMethod;
				oldArmy.PointsValueChanged -= PointsValueChangedMethod;
			}
		}
		private void UnitAddedMethod(object unitObj)
		{
			if (unitObj is Unit)
			{
				Unit unit = (Unit)unitObj;
				//TODO set error panel
				//sbErrorPanel.Text = "";
				//TODO add unit to tree
			}
		}

		private void UnitRemovedMethod(object unitObj)
		{
			if (unitObj is Unit)
			{
				Unit unit = (Unit)unitObj;
				//TODO set error panel
				//sbErrorPanel.Text = "";

				//TODO check if window is open, and close it if it is
				//TODO remove unit from tree
			}
		}
		
		private void PointsValueChangedMethod(WarFoundryObject obj, double oldVal, double newVal)
		{
			if (obj is Army)
			{
				SetPointsPanelText();
			}
		}

		private void CloseAllUnitWindows()
		{
//			FrmUnit[] unitForms = DictionaryUtils.ToArray(unitWindows);
//
//			foreach (FrmUnit window in unitForms)
//			{
//				window.Close();
//			}
//
//			unitWindows.Clear();
		}

		private void ListenToArmy(Army newArmy)
		{
			if (newArmy != null)
			{
				newArmy.UnitAdded += UnitAddedMethod;
				newArmy.UnitRemoved += UnitRemovedMethod;
				newArmy.PointsValueChanged += PointsValueChangedMethod;
			}
		}

		private void SetNullArmyState()
		{
			layout.actionSaveArmyAs.Enabled = false;
			layout.actionCloseArmy.Enabled = false;
			layout.menuExportArmyAs.Enabled = false;
			
			foreach (QAction exportAction in layout.menuExportArmyAs.Actions())
			{
				exportAction.Enabled = false;	
			}
			
			layout.armyTree.SetModel(new QStandardItemModel());
			DisableCategoryButtons();
		}

		void DisableCategoryButtons ()
		{
			//TODO handle category buttons
		}


		private void SetNonNullArmyState(Army newArmy)
		{
			SetCategoryButtons(newArmy.Race.Categories);
			EnableCategoryButtons();
			layout.actionSaveArmyAs.Enabled = true;
			layout.actionCloseArmy.Enabled = true;
			layout.menuExportArmyAs.Enabled = true;
			
			foreach (QAction exportAction in layout.menuExportArmyAs.Actions())
			{
				exportAction.Enabled = true;	
			}
			
			FillArmyTree(newArmy);
		}

		private void FillArmyTree (Army army)
		{
			QStandardItemModel treeModel = new QStandardItemModel();
			List<string> headers = new List<string>();
			headers.Add(army.Name);
			treeModel.SetHorizontalHeaderLabels(headers);
			
			foreach (var category in army.Categories)
			{
				QStandardItem item = CreateTreeItem(category);				
				treeModel.AppendRow(item);
				CreateUnitNodes(category, item);
			}
			
			layout.armyTree.SetModel(treeModel);
		}
		
		private QStandardItem CreateTreeItem(WarFoundryObject obj)
		{
			QVariant wrappedObject = QVariant.FromValue<WarFoundryObject>(obj);
			QStandardItem item = new QStandardItem(obj.Name);
			item.SetData(wrappedObject);
			return item;
		}

		private void CreateUnitNodes(ArmyCategory category, QStandardItem categoryItem)
		{
			foreach (Unit unit in category.GetUnits())
			{
				CreateTreeSubItem(unit, categoryItem);
			}
		}
		
		private QStandardItem CreateTreeSubItem(WarFoundryObject obj, QStandardItem rootItem)
		{
			QStandardItem item = CreateTreeItem(obj);
			rootItem.AppendRow(item);
			return item;
		}

		void SetCategoryButtons (Category[] categories)
		{
			//TODO create category buttons
		}
		
		void EnableCategoryButtons ()
		{
			//TODO enable category buttons
		}
		
		private void DoCloseArmy()
		{
			CloseCurrentArmy();
		}
		
		private bool CloseCurrentArmy()
		{
			bool closed = false;
			
			if (CurrentArmy!=null)
			{
				log.Debug("Closing "+CurrentArmy.Name);
				bool canClose = false;

				if (CommandStack.IsDirty())
				{
					log.Debug("Unsaved changes");
					string saveChanges = Translation.GetTranslation("SaveChangesQuestion", "the army \"{0}\" has been modified\r\nsave changes before closing army?", CurrentArmy.Name);
					string saveChangesTitle = Translation.GetTranslation("SaveChangesTitle", "unsaved changes");
					QMessageBox.StandardButton response = QMessageBox.Question(this, saveChangesTitle, saveChanges, (uint) (QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel), QMessageBox.StandardButton.Cancel);
					
					if (response == QMessageBox.StandardButton.Yes)
					{
						canClose = SaveCurrentArmy();
					}
					else if (response == QMessageBox.StandardButton.No)
					{
						log.Debug("User didn't save army");
						canClose = true;
					}
					//else they said cancel and we default to "canClose = false" so do nothing
				}
				else
				{
					canClose = true;
				}

				if (canClose)
				{
					CurrentArmy = null;
					closed = true;
				}
			
				log.Debug("Army "+(closed ? "" : "not")+" closed");
			}
			else
			{
				//pretend we succeeded
				closed = true;
			}
			
			return closed;
		}
		
		public Preferences Preferences
		{
			get { return preferences; }
			set { preferences = value; }
		}
		
		private void DoExportBasicHTML()
		{
			log.Debug("Exporting to HTML");
			QFileDialog fileDialog = new QFileDialog(this);
			fileDialog.acceptMode = QFileDialog.AcceptMode.AcceptSave;
			fileDialog.fileMode = QFileDialog.FileMode.AnyFile;
			fileDialog.SetDirectory(new QDir(Environment.GetFolderPath(Environment.SpecialFolder.Personal)));
			log.Debug("Requesting export path");
			int result = fileDialog.Exec();
			
			if (result == (int)QDialog.DialogCode.Accepted)
			{
				string path = fileDialog.SelectedFiles()[0];
				log.DebugFormat("Exporting to {0}", path);
				WarFoundryHtmlExporter.GetDefault().ExportArmy(CurrentArmy, path);
				log.Debug("Exported");
			}
		}
	}
}