context: 
  #region ================== Copyright (c) 2007 Pascal vd Heiden
  
  /*
   * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
   * This program is released under GNU General Public License
   * 
   * 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.
   * 
   */
  
  #endregion
  
  #region ================== Namespaces
  
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Drawing;
  using System.Text;
  using CodeImp.DoomBuilder.Properties;
  using System.IO;
  using CodeImp.DoomBuilder.IO;
  using System.Collections;
  using System.Reflection;
  using System.Windows.Forms;
  
  #endregion
  
  namespace CodeImp.DoomBuilder.Actions
  {
  	public class ActionManager
  	{
  		#region ================== Constants
  
  		private const string ACTIONS_RESOURCE = "Actions.cfg";
  
  		#endregion
  
  		#region ================== Variables
  
  		// Actions
  		private Dictionary<string, Action> actions;
  		
  		// Categories
  		private SortedDictionary<string, string> categories;
  		
  		// Keys state
  		private int modifiers;
  		private List<int> pressedkeys;
  		
  		// Begun actions
  		private List<Action> activeactions;
  		private Action currentaction;
  		
  		// Exclusive invokation
  		private bool exclusiverequested;
  		
  		// Disposing
  		private bool isdisposed = false;
  
  		#endregion
  
  		#region ================== Properties
  
  		internal SortedDictionary<string, string> Categories { get { return categories; } }
  		internal Action this[string action] { get { if(actions.ContainsKey(action)) return actions[action]; else throw new ArgumentException("There is no such action \"" + action + "\""); } }
  		public bool IsDisposed { get { return isdisposed; } }
  		internal bool ExclusiveRequested { get { return exclusiverequested; } }
  		
  		/// <summary>
  		/// Current executing action. This returns Null when no action is invoked.
  		/// </summary>
  		public Action Current { get { return currentaction; } internal set { currentaction = value; } }
  		
  		#endregion
  
  		#region ================== Constructor / Disposer
  
  		// Constructor
  		internal ActionManager()
  		{
  			// Initialize
  			Logger.WriteLogLine("Starting action manager...");
  			actions = new Dictionary<string, Action>();
  			pressedkeys = new List<int>();
  			activeactions = new List<Action>();
  			categories = new SortedDictionary<string, string>();
  			
  			// Load all actions in this assembly
  			LoadActions(General.ThisAssembly);
  			
  			// We have no destructor
  			GC.SuppressFinalize(this);
  		}
  
  		// Disposer
  		internal void Dispose()
  		{
  			// Not already disposed?
  			if(!isdisposed)
  			{
  				// Clean up
  				
  				// Done
  				isdisposed = true;
  			}
  		}
  
  		#endregion
  
  		#region ================== Actions
  
  		// This loads all actions from an assembly
  		internal void LoadActions(Assembly asm)
  		{
  			Stream actionsdata;
  			StreamReader actionsreader;
  			Configuration cfg;
  			string name, shortname;
  			bool debugonly;
  			string[] resnames;
  			AssemblyName asmname = asm.GetName();
  
  			// Find a resource named Actions.cfg
  			resnames = asm.GetManifestResourceNames();
  			foreach(string rn in resnames)
  			{
  				// Found one?
  				if(rn.EndsWith(ACTIONS_RESOURCE, StringComparison.InvariantCultureIgnoreCase))
  				{
  					// Get a stream from the resource
  					actionsdata = asm.GetManifestResourceStream(rn);
  					actionsreader = new StreamReader(actionsdata, Encoding.ASCII);
  
  					// Load configuration from stream
  					cfg = new Configuration();
  					cfg.InputConfiguration(actionsreader.ReadToEnd());
  					if(cfg.ErrorResult)
  					{
  						string errordesc = "Error in Actions configuration on line " + cfg.ErrorLine + ": " + cfg.ErrorDescription;
  						General.CancelAutoMapLoad();
  						General.ErrorLogger.Add(ErrorType.Error, "Unable to read Actions configuration from assembly " + Path.GetFileName(asm.Location));
  						Logger.WriteLogLine(errordesc);
  						General.ShowErrorMessage("Unable to read Actions configuration from assembly " + Path.GetFileName(asm.Location) + "!\n" + errordesc, MessageBoxButtons.OK);
  					}
  					else
  					{
  						// Read the categories structure
  						IDictionary cats = cfg.ReadSetting("categories", new Hashtable());
  						foreach(DictionaryEntry c in cats)
  						{
  							// Make the category if not already added
  							if(!categories.ContainsKey(c.Key.ToString()))
  								categories.Add(c.Key.ToString(), c.Value.ToString());
  						}
  
  						// Go for all objects in the configuration
  						foreach(DictionaryEntry a in cfg.Root)
  						{
  							// Get action properties
  							shortname = a.Key.ToString();
  							name = asmname.Name.ToLowerInvariant() + "_" + shortname;
  							debugonly = cfg.ReadSetting(a.Key + ".debugonly", false);
  
  							// Not the categories structure?
  							if(shortname.ToLowerInvariant() != "categories")
  							{
  								// Check if action should be included
  								if(General.DebugBuild || !debugonly)
  								{
  									// Create an action
  									CreateAction(cfg, name, shortname);
  								}
  							}
  						}
  					}
  					
  					// Done with the resource
  					actionsreader.Dispose();
  					actionsdata.Dispose();
  				}
  			}
  		}
  
  		// This manually creates an action
  		private void CreateAction(Configuration cfg, string name, string shortname)
  		{
  			// Action does not exist yet?
  			if(!actions.ContainsKey(name))
  			{
  				// Read the key from configuration
  				int key = General.Settings.ReadSetting("shortcuts." + name, -1);
  
  				// Create an action
  				actions.Add(name, new Action(cfg, name, shortname, key));
  			}
  			else
  			{
  				// Action already exists!
  				General.ErrorLogger.Add(ErrorType.Warning, "Action '" + name + "' already exists. Action names must be unique.");
  			}
  		}
  
  		// This binds all methods marked with this attribute
  		public void BindMethods(Type type)
  		{
  			// Bind static methods
  			BindMethods(null, type);
  		}
  
  		// This binds all methods marked with this attribute
  		public void BindMethods(object obj)
  		{
  			// Bind instance methods
  			BindMethods(obj, obj.GetType());
  		}
  
  		// This binds all methods marked with this attribute
  		private void BindMethods(object obj, Type type)
  		{
  			MethodInfo[] methods;
  			ActionAttribute[] attrs;
  			ActionDelegate del;
  			string actionname;
  
  			if(obj == null)
  				Logger.WriteLogLine("Binding static action methods for class " + type.Name + "...");
  			else
  				Logger.WriteLogLine("Binding action methods for " + type.Name + " object...");
  
  			// Go for all methods on obj
  			methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
  			foreach(MethodInfo m in methods)
  			{
  				// Check if the method has this attribute
  				attrs = (ActionAttribute[])m.GetCustomAttributes(typeof(BeginActionAttribute), true);
  
  				// Go for all attributes
  				foreach(ActionAttribute a in attrs)
  				{
  					// Create a delegate for this method
  					del = (ActionDelegate)Delegate.CreateDelegate(typeof(ActionDelegate), obj, m);
  
  					// Make proper name
  					actionname = a.GetFullActionName(type.Assembly);
  
  					// Bind method to action
  					if(Exists(actionname))
  						actions[actionname].BindBegin(del);
  					else
  						throw new ArgumentException("Could not bind " + m.ReflectedType.Name + "." + m.Name + " to action \"" + actionname + "\", that action does not exist! Refer to, or edit Actions.cfg for all available application actions.");
  				}
  				
  				// Check if the method has this attribute
  				attrs = (ActionAttribute[])m.GetCustomAttributes(typeof(EndActionAttribute), true);
  
  				// Go for all attributes
  				foreach(ActionAttribute a in attrs)
  				{
  					// Create a delegate for this method
  					del = (ActionDelegate)Delegate.CreateDelegate(typeof(ActionDelegate), obj, m);
  
  					// Make proper name
  					actionname = a.GetFullActionName(type.Assembly);
  
  					// Bind method to action
  					if(Exists(actionname))
  						actions[actionname].BindEnd(del);
  					else
  						throw new ArgumentException("Could not bind " + m.ReflectedType.Name + "." + m.Name + " to action \"" + actionname + "\", that action does not exist. Refer to, or edit Actions.cfg for all available application actions.");
  				}
  			}
  		}
  
  		// This binds a delegate manually
  		internal void BindBeginDelegate(Assembly asm, ActionDelegate d, BeginActionAttribute a)
  		{
  			string actionname;
  
  			// Make proper name
  			actionname = a.GetFullActionName(asm);
  
  			// Bind delegate to action
  			if(Exists(actionname))
  				actions[actionname].BindBegin(d);
  			else
  				General.ErrorLogger.Add(ErrorType.Warning, "Could not bind delegate for " + d.Method.Name + " to action \"" + a.ActionName + "\" (" + actionname + "), that action does not exist. Refer to, or edit Actions.cfg for all available application actions.");
  		}
  
  		// This binds a delegate manually
  		internal void BindEndDelegate(Assembly asm, ActionDelegate d, EndActionAttribute a)
  		{
  			string actionname;
  
  			// Make proper name
  			actionname = a.GetFullActionName(asm);
  
  			// Bind delegate to action
  			if(Exists(actionname))
  				actions[actionname].BindEnd(d);
  			else
  				General.ErrorLogger.Add(ErrorType.Warning, "Could not bind delegate for " + d.Method.Name + " to action \"" + a.ActionName + "\" (" + actionname + "), that action does not exist. Refer to, or edit Actions.cfg for all available application actions.");
  		}
  
  		// This unbinds all methods marked with this attribute
  		public void UnbindMethods(Type type)
  		{
  			// Unbind static methods
  			UnbindMethods(null, type);
  		}
  
  		// This unbinds all methods marked with this attribute
  		public void UnbindMethods(object obj)
  		{
  			// Unbind instance methods
  			UnbindMethods(obj, obj.GetType());
  		}
  
  		// This unbinds all methods marked with this attribute
  		private void UnbindMethods(object obj, Type type)
  		{
  			MethodInfo[] methods;
  			ActionAttribute[] attrs;
  			ActionDelegate del;
  			string actionname;
  
  			if(obj == null)
  				Logger.WriteLogLine("Unbinding static action methods for class " + type.Name + "...");
  			else
  				Logger.WriteLogLine("Unbinding action methods for " + type.Name + " object...");
  
  			// Go for all methods on obj
  			methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
  			foreach(MethodInfo m in methods)
  			{
  				// Check if the method has this attribute
  				attrs = (ActionAttribute[])m.GetCustomAttributes(typeof(BeginActionAttribute), true);
  
  				// Go for all attributes
  				foreach(ActionAttribute a in attrs)
  				{
  					// Create a delegate for this method
  					del = (ActionDelegate)Delegate.CreateDelegate(typeof(ActionDelegate), obj, m);
  
  					// Make proper name
  					actionname = a.GetFullActionName(type.Assembly);
  
  					// Unbind method from action
  					actions[actionname].UnbindBegin(del);
  				}
  				
  				// Check if the method has this attribute
  				attrs = (ActionAttribute[])m.GetCustomAttributes(typeof(EndActionAttribute), true);
  
  				// Go for all attributes
  				foreach(ActionAttribute a in attrs)
  				{
  					// Create a delegate for this method
  					del = (ActionDelegate)Delegate.CreateDelegate(typeof(ActionDelegate), obj, m);
  
  					// Make proper name
  					actionname = a.GetFullActionName(type.Assembly);
  
  					// Unbind method from action
  					actions[actionname].UnbindEnd(del);
  				}
  			}
  		}
  
  		// This unbinds a delegate manually
  		internal void UnbindBeginDelegate(Assembly asm, ActionDelegate d, BeginActionAttribute a)
  		{
  			string actionname;
  
  			// Make proper name
  			actionname = a.GetFullActionName(asm);
  
  			// Unbind delegate to action
  			actions[actionname].UnbindBegin(d);
  		}
  
  		// This unbinds a delegate manually
  		internal void UnbindEndDelegate(Assembly asm, ActionDelegate d, EndActionAttribute a)
  		{
  			string actionname;
  
  			// Make proper name
  			actionname = a.GetFullActionName(asm);
  
  			// Unbind delegate to action
  			actions[actionname].UnbindEnd(d);
  		}
  		
  		// This checks if a given action exists
  		public bool Exists(string action)
  		{
  			return actions.ContainsKey(action);
  		}
  
  		// This returns a list of all actions
  		internal Action[] GetAllActions()
  		{
  			Action[] list = new Action[actions.Count];
  			actions.Values.CopyTo(list, 0);
  			return list;
  		}
  
  		// This returns the specified action
  		public Action GetActionByName(string fullname)
  		{
  			return actions[fullname];
  		}
  		
  		// This saves the control settings
  		internal void SaveSettings()
  		{
  			// Go for all actions
  			foreach(KeyValuePair<string, Action> a in actions)
  			{
  				// Write to configuration
  				General.Settings.WriteSetting("shortcuts." + a.Key, a.Value.ShortcutKey);
  			}
  		}
  
  		// This invokes the Begin and End of the given action
  		public bool InvokeAction(string actionname)
  		{
  			if(Exists(actionname))
  			{
  				actions[actionname].Invoke();
  				return true;
  			}
  			else
  			{
  				return false;
  			}
  		}
  		
  		#endregion
  
  		#region ================== Shortcut Keys
  		
  		// This applies default keys if they are not already in use
  		internal void ApplyDefaultShortcutKeys()
  		{
  			// Find actions that have no key set
  			foreach(KeyValuePair<string, Action> a in actions)
  			{
  				// Key set?
  				if(a.Value.ShortcutKey == -1)
  				{
  					// Check if the default key is not already used
  					bool keyused = false;
  					foreach(KeyValuePair<string, Action> d in actions)
  					{
  						// Check if the keys are the same
  						// Note that I use the mask of the source action to check if they match any combination
  						if((d.Value.ShortcutKey & a.Value.ShortcutMask) == (a.Value.DefaultShortcutKey & a.Value.ShortcutMask))
  						{
  							// No party.
  							keyused = true;
  							break;
  						}
  					}
  					
  					// Party?
  					if(!keyused)
  					{
  						// Apply the default key
  						a.Value.SetShortcutKey(a.Value.DefaultShortcutKey);
  					}
  					else
  					{
  						// No party.
  						a.Value.SetShortcutKey(0);
  					}
  				}
  			}
  		}
  		
  		// This checks if a given action is active
  		public bool CheckActionActive(Assembly asm, string actionname)
  		{
  			if(asm == null) asm = General.ThisAssembly;
  			
  			// Find active action
  			string fullname = asm.GetName().Name.ToLowerInvariant() + "_" + actionname;
  			foreach(Action a in activeactions)
  			{
  				if(a.Name == fullname) return true;
  			}
  			
  			// No such active action
  			return false;
  		}
  		
  		// Removes all shortcut keys
  		internal void RemoveShortcutKeys()
  		{
  			// Clear all keys
  			foreach(KeyValuePair<string, Action> a in actions)
  				a.Value.SetShortcutKey(0);
  		}
  		
  		// This notifies a key has been pressed
  		// Returns true when the key press has been absorbed
  		internal bool KeyPressed(int key)
  		{
  			int strippedkey = key & ~((int)Keys.Alt | (int)Keys.Shift | (int)Keys.Control);
  			if((strippedkey == (int)Keys.ShiftKey) || (strippedkey == (int)Keys.ControlKey)) key = strippedkey;
  			bool repeat = pressedkeys.Contains(strippedkey);
  			
  			// Update pressed keys
  			if(!repeat) pressedkeys.Add(strippedkey);
  			
  			// Add action to active list
  
gt: 			Action[] acts = GetActionsByKey(key);