mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-30 07:31:36 +00:00
bafb8ed511
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.
708 lines
20 KiB
C#
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
|
|
}
|
|
}
|