2009-04-19 18:07:22 +00:00
#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 ;
2009-05-02 14:59:05 +00:00
private Action currentaction ;
2009-04-19 18:07:22 +00:00
// Exclusive invokation
private bool exclusiverequested ;
// Disposing
2013-07-19 15:30:58 +00:00
private bool isdisposed ;
2009-04-19 18:07:22 +00:00
#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 ; } }
2009-05-02 14:59:05 +00:00
/// <summary>
/// Current executing action. This returns Null when no action is invoked.
/// </summary>
public Action Current { get { return currentaction ; } internal set { currentaction = value ; } }
2009-04-19 18:07:22 +00:00
#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 ;
2013-03-18 13:52:27 +00:00
string name , shortname ;
bool debugonly ;
2009-04-19 18:07:22 +00:00
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 ( ) ) ;
2009-07-28 13:40:30 +00:00
if ( cfg . ErrorResult )
2009-04-19 18:07:22 +00:00
{
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 ( ) ;
2014-01-23 13:36:51 +00:00
break ; //mxd. Usually we have a single "Actions.cfg", right?
2009-04-19 18:07:22 +00:00
}
}
}
// 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
2009-05-16 16:45:28 +00:00
public void BindMethods ( Type type )
2009-04-19 18:07:22 +00:00
{
// Bind static methods
BindMethods ( null , type ) ;
}
// This binds all methods marked with this attribute
2009-05-16 16:45:28 +00:00
public void BindMethods ( object obj )
2009-04-19 18:07:22 +00:00
{
// 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
2009-05-16 16:45:28 +00:00
public void UnbindMethods ( Type type )
2009-04-19 18:07:22 +00:00
{
// Unbind static methods
UnbindMethods ( null , type ) ;
}
// This unbinds all methods marked with this attribute
2009-05-16 16:45:28 +00:00
public void UnbindMethods ( object obj )
2009-04-19 18:07:22 +00:00
{
// 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
2010-08-12 21:00:54 +00:00
// Returns true when the key press has been absorbed
internal bool KeyPressed ( int key )
2009-04-19 18:07:22 +00:00
{
int strippedkey = key & ~ ( ( int ) Keys . Alt | ( int ) Keys . Shift | ( int ) Keys . Control ) ;
2014-01-03 12:54:52 +00:00
if ( ( strippedkey = = ( int ) Keys . ShiftKey ) | | ( strippedkey = = ( int ) Keys . ControlKey ) | | ( strippedkey = = ( int ) Keys . Alt ) ) key = strippedkey ;
2009-04-19 18:07:22 +00:00
bool repeat = pressedkeys . Contains ( strippedkey ) ;
// Update pressed keys
if ( ! repeat ) pressedkeys . Add ( strippedkey ) ;
// Add action to active list
Action [ ] acts = GetActionsByKey ( key ) ;
2010-08-12 21:00:54 +00:00
bool absorbed = acts . Length > 0 ;
2009-04-19 18:07:22 +00:00
foreach ( Action a in acts ) if ( ! activeactions . Contains ( a ) ) activeactions . Add ( a ) ;
// Invoke actions
2010-08-12 21:00:54 +00:00
absorbed | = BeginActionByKey ( key , repeat ) ;
return absorbed ;
2009-04-19 18:07:22 +00:00
}
// This notifies a key has been released
2010-08-12 21:00:54 +00:00
// Returns true when the key release has been absorbed
internal bool KeyReleased ( int key )
2009-04-19 18:07:22 +00:00
{
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
2010-08-12 21:00:54 +00:00
return EndActiveActions ( ) ;
2009-04-19 18:07:22 +00:00
}
// 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
2010-08-12 21:00:54 +00:00
// Returns true when the key invokes any action
internal bool BeginActionByKey ( int key , bool repeated )
2009-04-19 18:07:22 +00:00
{
2010-08-12 21:00:54 +00:00
bool invoked = false ;
2009-04-19 18:07:22 +00:00
// 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 ) )
{
2010-08-12 21:00:54 +00:00
invoked = true ;
2009-04-19 18:07:22 +00:00
// 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!");
}
}
}
2010-08-12 21:00:54 +00:00
return invoked ;
2009-04-19 18:07:22 +00:00
}
// This will end active actions for which the pressed keys do not match
2010-08-12 21:00:54 +00:00
// Returns true when actions have been ended
private bool EndActiveActions ( )
2009-04-19 18:07:22 +00:00
{
bool listchanged ;
2010-08-12 21:00:54 +00:00
bool actionsended = false ;
2009-04-19 18:07:22 +00:00
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 )
{
2010-08-12 21:00:54 +00:00
actionsended = true ;
2009-04-19 18:07:22 +00:00
activeactions . RemoveAt ( i ) ;
listchanged = true ;
a . End ( ) ;
break ;
}
}
}
while ( listchanged ) ;
2010-08-12 21:00:54 +00:00
return actionsended ;
2009-04-19 18:07:22 +00:00
}
// 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
}
}