UltimateZoneBuilder/Source/Core/Actions/ActionManager.cs
MaxED bafb8ed511 UDMF: sidedef flags are now defined in game configuration instead of being hardcoded.
Texture scale controls now have "Link" button. 
Thing, Sector (UDMF) and Linedef edit forms now work in realtime mode.
Some more fixes in Edit form realtime update stuff.
Removed some unused variables and functions.
2013-07-19 15:30:58 +00:00

708 lines
20 KiB
C#

#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.Text;
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;
#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
General.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));
General.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)
General.WriteLogLine("Binding static action methods for class " + type.Name + "...");
else
General.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)
General.WriteLogLine("Unbinding static action methods for class " + type.Name + "...");
else
General.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
Action[] acts = GetActionsByKey(key);
bool absorbed = acts.Length > 0;
foreach(Action a in acts) if(!activeactions.Contains(a)) activeactions.Add(a);
// Invoke actions
absorbed |= BeginActionByKey(key, repeat);
return absorbed;
}
// This notifies a key has been released
// Returns true when the key release has been absorbed
internal bool KeyReleased(int key)
{
int strippedkey = key & ~((int)Keys.Alt | (int)Keys.Shift | (int)Keys.Control);
// Update pressed keys
if(pressedkeys.Contains(strippedkey)) pressedkeys.Remove(strippedkey);
// End actions that no longer match
return EndActiveActions();
}
// This releases all pressed keys
internal void ReleaseAllKeys()
{
// Clear pressed keys
pressedkeys.Clear();
// End actions
EndActiveActions();
}
// This updates the modifiers
internal void UpdateModifiers(int mods)
{
// Update modifiers
modifiers = mods;
// End actions that no longer match
EndActiveActions();
}
// This will call the associated actions for a keypress
// Returns true when the key invokes any action
internal bool BeginActionByKey(int key, bool repeated)
{
bool invoked = false;
// Get all actions for which a begin is bound
List<Action> boundactions = new List<Action>(actions.Count);
foreach(KeyValuePair<string, Action> a in actions)
if(a.Value.BeginBound) boundactions.Add(a.Value);
// Go for all actions
foreach(Action a in boundactions)
{
// This action is associated with this key?
if(a.KeyMatches(key))
{
invoked = true;
// Allowed to repeat?
if(a.Repeat || !repeated)
{
// Invoke action
a.Begin();
}
else
{
//General.WriteLogLine("Action \"" + a.Value.Name + "\" failed because it does not support repeating activation!");
}
}
}
return invoked;
}
// This will end active actions for which the pressed keys do not match
// Returns true when actions have been ended
private bool EndActiveActions()
{
bool listchanged;
bool actionsended = false;
do
{
// Go for all active actions
listchanged = false;
for(int i = 0; i < activeactions.Count; i++)
{
Action a = activeactions[i];
// Go for all pressed keys
bool stillactive = false;
foreach(int k in pressedkeys)
{
if((k == (int)Keys.ShiftKey) || (k == (int)Keys.ControlKey))
stillactive |= a.KeyMatches(k);
else
stillactive |= a.KeyMatches(k | modifiers);
}
// End the action if no longer matches any of the keys
if(!stillactive)
{
actionsended = true;
activeactions.RemoveAt(i);
listchanged = true;
a.End();
break;
}
}
}
while(listchanged);
return actionsended;
}
// This returns all action names for a given key
public string[] GetActionNamesByKey(int key)
{
List<string> actionnames = new List<string>();
// Go for all actions
foreach(KeyValuePair<string, Action> a in actions)
{
// This action is associated with this key?
if(a.Value.KeyMatches(key))
{
// List short name
actionnames.Add(a.Value.ShortName);
}
}
// Return result;
return actionnames.ToArray();
}
// This returns all action names for a given key
public Action[] GetActionsByKey(int key)
{
List<Action> actionnames = new List<Action>();
// Go for all actions
foreach(KeyValuePair<string, Action> a in actions)
{
// This action is associated with this key?
if(a.Value.KeyMatches(key))
{
// List short name
actionnames.Add(a.Value);
}
}
// Return result;
return actionnames.ToArray();
}
#endregion
#region ================== Exclusive Invokation
// This resets the exclusive request
internal void ResetExclusiveRequest()
{
exclusiverequested = false;
}
/// <summary>
/// This asks for exclusive invokation of the current BeginAction or EndAction.
/// Returns true when successull, false when denied (already given to another caller)
/// </summary>
public bool RequestExclusiveInvokation()
{
if(exclusiverequested)
{
// Already given out
return false;
}
else
{
// Success
exclusiverequested = true;
return true;
}
}
#endregion
}
}