% MagicListener object
%
% MagicListener constructor creates a listener just like the addlistener
% function. The difference is the constructor accepts one more input, one
% object or a cell of objects, and the life cycle of the returned object
% will be linked also on these objects.
%
% Exemple:
%
% Without MagicListener:
%
% [Class1.m]
% classdef Class1 < handle
% events
% myEvent
% end
% end
%
% [Class2.m]
% classdef Class2 < handle
% methods
% function this = Class2( obj1 )
% addlistener( obj1, 'myEvent', @(H,E) this.callback() );
% end
% function callback( this )
% disp( 'CALLBACK!' );
% end
% end
% end
%
% >> obj1 = Class1();
% >> obj2 = Class2( obj1 );
% >> notify( obj1, 'myEvent' );
% CALLBACK!
% >> delete( obj2 );
% >> notify( obj1, 'myEvent' );
% Warning: Error occurred while executing callback:
% Invalid or deleted object.
%
% With MagicListener:
%
% [Class1.m]
% classdef Class1 < handle
% events
% myEvent
% end
% end
%
% [Class2.m]
% classdef Class2 < handle
% methods
% function this = Class2( obj1 )
% MagicListener( obj1, 'myEvent', @(H,E) this.callback(), this );
% end
% function callback( this )
% disp( 'CALLBACK!' );
% end
% end
% end
%
% >> obj1 = Class1();
% >> obj2 = Class2( obj1 );
% >> notify( obj1, 'myEvent' );
% CALLBACK!
% >> delete( obj2 );
% >> notify( obj1, 'myEvent' );
%
classdef MagicListener < handle
%% Public properties
properties( Dependent )
Source
EventName
Callback
Enabled
Recursive
LifeCycleObj
end
methods
function set.Source( this, value )
this.listenerObj.Source = value;
end
function value = get.Source( this )
value = this.listenerObj.Source;
end
function set.EventName( this, value )
this.listenerObj.EventName = value;
end
function value = get.EventName( this )
value = this.listenerObj.EventName;
end
function set.Callback( this, value )
this.listenerObj.Callback = value;
end
function value = get.Callback( this )
value = this.listenerObj.Callback;
end
function set.Enabled( this, value )
this.listenerObj.Enabled = value;
end
function value = get.Enabled( this )
value = this.listenerObj.Enabled;
end
function set.Recursive( this, value )
this.listenerObj.Source = value;
end
function value = get.Recursive( this )
value = this.listenerObj.Recursive;
end
function set.LifeCycleObj( this, value )
this.lifeCycleListenerObj.Source = value;
end
function value = get.LifeCycleObj( this )
value = this.lifeCycleListenerObj.Source;
end
end
%% Private properties
properties( Access = private )
listenerObj = [];
lifeCycleListenerObj = [];
end
%% Constructor and destructor
methods
function this = MagicListener( source, varargin )
% ml = MagicListener( source, [property], event, callback, [lifeCycleObj] )
if nargin == 0
return
end
import event.listener
import event.proplistener
% load input parameters
error( nargchk( 3, 5, nargin, 'struct' ) );
propertyEvent = false;
lifecycle = {};
if ischar( varargin{2} )
propertyEvent = true;
property = varargin{1};
event = varargin{2};
callback = varargin{3};
if length( varargin ) == 4
lifecycle = varargin{4};
end
else
event = varargin{1};
callback = varargin{2};
if length( varargin ) == 3
lifecycle = varargin{3};
end
end
isDestroyEvent = strcmp( event, 'ObjectBeingDestroyed' );
if isDestroyEvent
finalCallback = @deleteAfterCallback;
else
finalCallback = @trycatchCallback;
end
% set source and lifeCycleObj in cell array
if ~iscell( source )
if numel( source ) == 1
source = { source };
else
source = num2cell( source );
end
end
if ~iscell( lifecycle )
if numel( lifecycle ) == 1
lifecycle = { lifecycle };
else
lifecycle = num2cell( lifecycle );
end
end
if ~isDestroyEvent
lifecycle = [ source lifecycle ];
end
% create listener
if propertyEvent
nbSource = length( source );
properties = cell( 1, nbSource );
for prop = 1:nbSource
metaSource = metaclass( source{prop} );
propSource = metaSource.Properties;
numProp = cellfun( @(prop) strcmp( prop.Name, property ), propSource );
properties{prop} = propSource{ numProp };
end
this.listenerObj = proplistener( source, properties, event, finalCallback );
else
this.listenerObj = listener( source, event, finalCallback );
end
% create lifecycle listener
this.lifeCycleListenerObj = listener( lifecycle, 'ObjectBeingDestroyed', @(H,E) destroy() );
MagicListener.manageMagicListener( 'add', this )
function trycatchCallback( H, E )
try
callback( H, E )
catch err
getReport( err )
end
end
function deleteAfterCallback( H, E )
try
callback( H, E )
catch err
getReport( err )
end
destroy();
end
function destroy()
if isvalid( this )
delete( this );
end
end
end
function delete( this )
if isvalid( this )
MagicListener.manageMagicListener( 'rem', this )
delete( this.listenerObj );
delete( this.lifeCycleListenerObj );
end
end
end
%% Kill 'em all!!
methods( Static )
function killall()
MagicListener.manageMagicListener( 'remall' );
end
end
%% Management (record and delete - static)
methods( Static, Access = private )
function manageMagicListener( action, magicListener )
persistent recursiveFlag bufferSize buffer listeners listenersSize nbListeners
if isempty( recursiveFlag )
recursiveFlag = false;
initialize()
end
function initialize()
bufferSize = 100;
temp(1, 1:bufferSize) = MagicListener();
buffer = temp;
listeners = buffer;
listenersSize = bufferSize;
nbListeners = 0;
end
if recursiveFlag
return
else
recursiveFlag = true; %#ok<NASGU>
end
switch action
case 'add'
if nbListeners+1 > listenersSize
if isempty( buffer )
initialize();
else
listeners = [ listeners buffer ];
listenersSize = listenersSize + bufferSize;
end
end
nbListeners = nbListeners + 1;
listeners( nbListeners ) = magicListener;
case 'rem'
listeners( listeners == magicListener ) = [];
nbListeners = nbListeners - 1;
listenersSize = listenersSize - 1;
case 'remall'
for i = 1:nbListeners
delete( listeners(i) );
end
buffer = [];
listeners = [];
listenersSize = 0;
nbListeners = 0;
end
recursiveFlag = false;
end
end
end