[DefaultBindingProperty( focusedProperty )]
public abstract class CommandObserver : Switchable, INotifyPropertyChanged
{
const string focusedProperty = "Focused";
const string activeProperty = "IsActive";
const string lispName = "*LISP*";
Document doc = null;
DocumentCollection docs = Application.DocumentManager;
bool handled = false;
string current = null;
string nested = null;
State state = State.None;
bool ignoreLisp = false;
public CommandObserver( Document doc, bool enabled = true )
: base( false )
{
if( doc == null )
throw new ArgumentNullException
( "doc" ); this.doc = doc;
base.Enabled = enabled;
}
public enum Status
{
Starting = 0,
Ended = 1,
Cancelled = 2,
Failed = 3
}
[Flags]
public enum State
{
None = 0,
/// <summary>
/// The observed command has been
/// started and has not yet ended.
/// </summary>
Active = 1,
/// <summary>
/// The observed command is active and
/// is in use in the active document.
/// </summary>
Focused = 2,
/// <summary>
/// The observed command is suspended while
/// a transparently-issued command or LISP
/// expression is in progress.
/// </summary>
Modeless = 4,
/// <summary>
/// The document in which an active observed
/// command was started, is currently not the
/// active document.
/// </summary>
Document = 8,
/// <summary>
/// The observed command is suspended while
/// a LISP expression is being evaluated.
/// </summary>
Lisp = 16
}
public Document Document
{
get
{
return this.doc;
}
}
public State CurrentState
{
get
{
return state;
}
}
/// <summary>
///
/// True = the observed command has started, the Document
/// in which the observed command was started is active,
/// and the observed command is not currently suspended
/// while another transparently-issued command or LISP
/// expression is active.
///
/// This property can change between the points when an
/// observed command starts and ends, depending on if
/// a transparently-issued command is in-progress, LISP
/// is running, and if the document in which the observed
/// command was started is currently active.
///
/// </summary>
[Bindable( true )]
public bool Focused
{
get
{
return state.HasFlag( State.Focused );
// return this.doc.IsActive && this.current != null && this.nested == null;
}
}
/// <summary>
/// True = An observed command was started, but has not yet
/// ended. The command may not be focused (because the document
/// is not the active document, or because the observed command
/// was interrupted by another currently-active command or LISP
/// function that was issued transparently).
///
/// Unlike the Focused property, this property's value remains
/// true between the point where an observed command is started
/// and the point where it ends.
///
/// </summary>
[Bindable( true )]
public bool IsActive
{
get
{
return this.current != null;
}
}
/// <summary>
///
/// The active observed command, or an
/// empty string if no observed command
/// is active.
///
/// </summary>
[Bindable( true )]
public string Current
{
get
{
return this.current ?? string.Empty;
}
}
/// <summary>
///
/// The name of the transparently-issued command
/// that has interrupted the observed command, while
/// the observed command is interrupted.
///
/// The value of this property is not necessarily the
/// name of a currently active, transparently-issued
/// command. The names of nested transparently-issued
/// commands or LISP expressions that do not change
/// the Focused state of the observed command are not
/// returned in this property.
///
/// A 'nested' transparently-issued command is a command
/// that was transparently issued while another transparently-
/// issued command was in progress.
///
/// If the observed command was interrupted by a LISP
/// expression, the value of this property will be the
/// value of the LispName property
///
/// </summary>
public string Nested
{
get
{
return this.nested ?? string.Empty;
}
}
/////////////////////////////////////////////////////////////
/// Overridables:
/// <summary>
///
/// <warning>
/// It is critically-necessary for overrides in
/// all derived types to supermessage this.
/// </warning>
///
/// Called when the instance is enabled or disabled
/// by setting the Enabled property.
///
/// When the instance is not enabled, all command-
/// related activity, including starting of commands,
/// is disabled.
///
/// </summary>
protected override void OnEnabledChanged( bool value )
{
if( value )
{
this.doc.CommandWillStart += commandWillStart;
}
else
{
this.doc.CommandWillStart -= commandWillStart;
RemoveHandlers();
}
NotifyPropertyChanged( "Enabled" );
}
/// <summary>
///
/// Indicates that a command is starting. overrides
/// should return true to indicate if the command
/// that is starting should be observed.
///
/// If false is returned, the command's state is not
/// observed and all other notifications, including
/// when the command ends are not sent.
///
/// </summary>
/// <param name="name">The global name of the command that is starting</param>
/// <returns>A value indicating if the command should be observed</returns>
protected abstract bool OnCommandStarting( string name );
/// <summary>
/// Indicates that an observed command has ended.
/// </summary>
/// <param name="name">The name of the command that has ended</param>
/// <param name="status">A value indicating how the command ended</param>
protected virtual void OnCommandEnded( string name, Status status )
{
}
/// <summary>
///
/// Called when the 'focused' state of an observed
/// command changes.
///
/// An observed command is in the focused state
/// when all of the following conditions are true:
///
/// - The observed command has been started,
/// and has not yet ended.
///
/// - The document in which the observed
/// command was started is the active
/// document.
///
/// - The observed command is not currently
/// suspended while another transparently-
/// issued command or LISP expression is
/// active or in-progress.
///
/// OnFocusdChanged() is called when a change
/// in any of the above conditions causes the
/// focused state of an observed command to
/// change.
///
/// Specifically:
///
/// - When the observed comand starts and ends.
///
/// - When a transparent command starts or ends
/// while the observed command is in progress,
/// but not when additional commands are issued
/// transparently while a transparently-issued
/// command is active.
///
/// - When the document in which an observed command
/// was started is activated or deactivated, while
/// no transparently-issued command is active.
///
/// Nested, transparently-issued commands that
/// are issued while other transparently-issued
/// commands are active, do not alter the focused
/// state of the observed command.
///
/// Changes to the active document that occur
/// while a transparently-issued command is in
/// progress, do not alter the focused state of
/// the observed command.
///
/// observed is the name of the observed command.
///
/// The focused parameter indicates if the observed
/// command has become focused, as it is defined
/// above. True means the command has entered the
/// focused state. False indicates the command has
/// left the focused state.
///
/// state indicates the condition(s) that changed,
/// resulting in a change to the the focused state,
/// as follows:
///
/// State.Active: The observed command started or ended
///
/// State.Modeless: The observed command was interrupted
/// when a transparently-issued command was started, or
/// was resumed when a transparently-issued command ended.
///
/// State.Lisp: The observed command was interrupted
/// when a LISP expression was evaluated, or was resumed
/// when the LISP expression's evaluation is completed.
/// When this flag is present, the Modeless flag is also
/// present.
///
/// State.Document: The active state of the document in
/// which the observed command was started has changed.
///
/// </summary>
protected virtual void OnFocusChanged( string observed, bool focused, State state )
{
}
/// <summary>
///
/// Called when an observed command that is currently
/// active, is suspended or interrupted as a result of
/// another command having been issued transparently,
/// or by evaluation of LISP.
///
/// nested is the name of the interrupting, transparently-
/// issued command that is starting. If the interruption
/// is a result of a LISP expression, the value of this
/// parameter will be the value of the LispName property.
///
/// </summary>
protected virtual void OnSuspend( string current, string nested )
{
}
/// <summary>
///
/// Called when an observed command that is currently
/// in-progress and has been suspended or interrupted
/// by the use of a transparent command or LISP, is
/// resumed as a result of the transparent command or
/// LISP ending.
///
/// The first argument is the name of the active observed
/// command.
///
/// The second argument is the name of the transparently-
/// issued command that is ending, or the value of the
/// LispName property if the interruption was triggered
/// by evalution of a LISP expression.
///
/// </summary>
protected virtual void OnResume( string current, string nested )
{
}
/// <summary>
///
/// Called when a document containing the Focused, observed
/// command is deactivated, as a result of a change to the
/// active document. This notification is sent only if the
/// observed command is focused. If the observed command is
/// not currently focused (e.g., the command is suspended
/// while one or more transparently-issued commands are in
/// progress) the notification is not sent.
///
/// Calls to this notification are followed by a call to
/// the OnDocumentActivated() method when the document is
/// subsequently activated again.
///
/// </summary>
protected virtual void OnDocumentDeactivated( string current )
{
}
/// <summary>
///
/// Called when a non-active document containing a Focused,
/// observed command is activated. This notification is sent
/// only if the observed command is focused. If the observed
/// command is not currently focused (e.g., the command is
/// suspended while one or more transparently-issued commands
/// are in progress) the notification is not sent.
///
/// Calls to this notification are always preceded by a call
/// to the OnDocumentDeactivated() method.
///
/// </summary>
protected virtual void OnDocumentActivated( string current )
{
}
/////////////////////////////////////////////////////////////
/// Non-public implementation
void SetState( State flags, bool value = true )
{
if( value )
state |= flags;
else
state &= ~flags;
}
void commandWillStart( object sender, CommandEventArgs e )
{
OnStarting( e.GlobalCommandName );
}
void OnStarting( string name )
{
if( current == null ) // ! state.HasFlag( State.Active ) )
{
if( OnCommandStarting( name ) )
{
current = name;
AddHandlers();
state = State.Active;
StateChanged( true, State.Active );
}
}
else if( nested == null ) // ! state.HasFlag( State.Modeless ) )
{
nested = name;
OnSuspend( current, nested );
State changing = GetModelessState( name );
SetState( changing );
StateChanged( false, changing );
}
}
void OnEnding( string name, Status status )
{
if( name == current )
{
try
{
RemoveHandlers();
StateChanged( false, State.Active );
state = State.None;
OnCommandEnded( name, status );
}
finally
{
current = null;
}
}
else if( name == nested )
{
try
{
State changing = GetModelessState( name );
SetState( changing, false );
StateChanged( true, changing );
OnResume( current, name );
}
finally
{
nested = null;
}
}
}
State GetModelessState( string name )
{
return name == lispName ? State.Modeless | State.Lisp : State.Modeless;
}
void documentActivated( object sender, DocumentCollectionEventArgs e )
{
if( e.Document == this.doc )
{
if( nested == null ) // ( state & ~ State.Document ) == State.Active )
{
OnDocumentActivated( current );
StateChanged( true, State.Document );
}
SetState( State.Document, false );
}
}
void documentToBeDeactivated( object sender, DocumentCollectionEventArgs e )
{
if( e.Document == this.doc )
{
if( nested == null ) // state == State.Active ) // nested == null )
{
OnDocumentDeactivated( current );
StateChanged( false, State.Document );
}
SetState( State.Document );
}
}
void StateChanged( bool focused, State changed )
{
SetState( State.Focused, focused );
OnFocusChanged( this.current, focused, changed );
if( PropertyChanged != null )
{
NotifyPropertyChanged( focusedProperty );
if( changed == State.Active )
NotifyPropertyChanged( activeProperty );
}
MyUtils.WriteDebugMessage( "*** OnFocusedChanged( {0}, {1}, {2} ) ***",
current, focused, changed );
}
void NotifyPropertyChanged( string name )
{
if( PropertyChanged != null )
PropertyChanged
( this,
new PropertyChangedEventArgs
( name
) ); }
void AddHandlers()
{
if( !this.handled )
{
this.handled = true;
doc.CommandEnded += commandEnded;
doc.CommandFailed += commandFailed;
doc.CommandCancelled += commandCancelled;
docs.DocumentActivated += documentActivated;
docs.DocumentToBeDeactivated += documentToBeDeactivated;
if( ! ignoreLisp )
{
doc.LispWillStart += lispWillStart;
doc.LispEnded += lispEnded;
doc.LispCancelled += lispCancelled;
}
}
}
void RemoveHandlers()
{
if( this.handled )
{
this.handled = false;
doc.CommandEnded -= commandEnded;
doc.CommandFailed -= commandFailed;
doc.CommandCancelled -= commandCancelled;
docs.DocumentToBeDeactivated -= documentToBeDeactivated;
docs.DocumentActivated -= documentActivated;
if( ! ignoreLisp )
{
doc.LispWillStart -= lispWillStart;
doc.LispEnded -= lispEnded;
doc.LispCancelled -= lispCancelled;
}
}
}
public bool IgnoreLisp
{
get
{
return ignoreLisp;
}
set
{
ignoreLisp = value;
}
}
public static string LispName
{
get
{
return lispName;
}
}
public static bool IsLisp( string name )
{
return name == lispName;
}
void lispWillStart( object sender, LispWillStartEventArgs e )
{
OnStarting( lispName );
}
void lispCancelled( object sender, EventArgs e )
{
OnEnding( lispName, Status.Cancelled );
}
void lispEnded( object sender, EventArgs e )
{
OnEnding( lispName, Status.Ended );
}
void commandCancelled( object sender, CommandEventArgs e )
{
OnEnding( e.GlobalCommandName, Status.Cancelled );
}
void commandFailed( object sender, CommandEventArgs e )
{
OnEnding( e.GlobalCommandName, Status.Failed );
}
void commandEnded( object sender, CommandEventArgs e )
{
OnEnding( e.GlobalCommandName, Status.Ended );
}
public event PropertyChangedEventHandler PropertyChanged;
}