%
% Add/remove arbitrary number of callbacks on a callback-stack for objects of type ...
%
% figure, axes, line, patch, surface, text, image, zoom, pan, rotate.
%
% Using this utility makes it possible to add multiple callbacks to the callback-function of an objects
% without overwriting any already existing callbacks.
%
% SYNTAX
% ======
% CallbackStack(obj, cb_type, callback [,'remove' or 'add'])
%
% ARGUMENTS
% =========
% obj (Vector of) object-handle(s) or object(s) as returned from z = zoom; p = pan; r = rotate3d;
% cb_type Depends on object type, e.g. 'ButtonDownFcn', if obj is an axis.
% callback Function handle to be evaluated as action callback (s. NOTES below !!!)
% 'remove' Optional argument to remove already existing callback(s) instead of adding it (s. NOTES).
% 'add' Optional argument to ALLWAYS add callback, even if it already exists (s. NOTES).
%
% EXAMPLES (execute one after the other and trigger the callbacks afterwards by zooming, paning or clicking)
% ========
% gca; % Make sure that there is an existing axis
% CallbackStack(pan, 'ActionPostCallback','disp(1)') % Create first callback for pan-object
% CallbackStack(zoom, 'ActionPostCallback','disp(2)') % Create first callback for zoom-object
% CallbackStack([zoom pan],'ActionPostCallback','disp(3)') % Additional callbacks for pan & zoom !!!
% CallbackStack(pan, 'ActionPostCallback','disp(1)','remove') % Now remove one callback
%
% CallbackStack([gcf gca], 'ButtonDownFcn','disp(''Hello'')') % Same as above, but for figure and axis-objects
% CallbackStack(gca, 'ButtonDownFcn','disp(''World'')')
%
% NOTES
% =====
% - Callbacks may be specified as strings (s. examples), simple function handles (@MyFcn), cell-array
% of function handle and arguments ( {@MyFcn,arg1,arg2, ...} ). The use of anonymous function
% handles may work, but has not been tested ...
% - "CallbackStack(gca, 'Button', 'disp(1)')" will cast error, since no abbreviatons of callback-names are allowed,
% i.e. 'ButtonDownFcn' => 'Button' is not allowed
% - It is completely your responsibility to make sure that different callbacks do not interfere with each other !!!
% - You may only specify 'remove' OR 'add' as input argument, not both at the same time
%
% AUTHOR
% ======
% Sebastian Hlz (XXX_shoelzATifm-geomar.de_YYY)
%
% BUGS & ToDo
% ===========
%
% VERSION
% =======
% 21.01.2009 0.92 Removed bug in add-switch.
% 10.01.2009 0.91 Callbacks are now casted to the form {@fhandle, 'arg1', ..., argN)
% This makes it easier to compare the callback-stack and extends the scope of the function.
% 01.10.2008 0.9 First Release, please report any bugs.
%
function CallbackStack(varargin)
% Parse input
try [obj,cb_type,callback, add,remove] = local_ParseInput(varargin{:}); catch rethrow(lasterror); end
% The callback of each object is redirected to -> "function local_callback"
for i=1:length(obj)
% Get current callback
CC = get(obj(i),cb_type);
if isempty(CC)
CBs = {};
elseif iscell(CC) && length(CC)==4 && isa(CC{1}, 'function_handle') && isequal(CC{1}, @local_callback)
CBs = CC{end};
else
CBs = {CC};
end
% Check, if this callback has already been added
if remove && ~isempty(CBs)
for j = length(CBs):-1:1
if isequal(CBs{j},callback)
CBs(j) = [];
end
end
end
% Add callback to structure
if add; CBs{end+1} = callback; end %#ok<AGROW>
set(obj(i),cb_type,{@local_callback, obj(i), cb_type, CBs});
end
end
% ==========================================================
function local_callback(dummy1, dummy2, obj, cb_type, CBs)
for i_cb = 1:length(CBs)
if ischar(CBs{i_cb})
eval (CBs{i_cb})
elseif iscell(CBs{i_cb})
feval(CBs{i_cb}{:})
elseif isa(CBs{i_cb},'function_handle')
feval(CBs{i_cb})
else
warning('Unsupported type of function specification'); %#ok<WNTAG>
disp(CBs{i_cb})
end
end
end
% ==========================================================
function [OBJ,cb_type,callback,add,remove] = local_ParseInput(varargin)
error(nargchk(3, 4, nargin))
add = 1; remove = 1;
if nargin==4 && strcmpi(varargin{4},'remove'); add = 0; end
if nargin==4 && strcmpi(varargin{4},'add'); remove = 0; end
OBJ = varargin{1};
for i = 1:length(OBJ)
obj = OBJ(i);
% Expand the errorchecking in this loop, if you need to add callbacks to objects,
% which are currently not implemented here (e.g. for the timer-object ...)
if any(strcmp({'graphics.pan' 'graphics.zoom' 'graphics.rotate3d'}, class(obj)))
% Note that these callbacks are only available for Matlab 7.3 (?!) or higher
valid_callbacks = {'ActionPreCallback' 'ActionPostCallback' 'ButtonDownFilter'};
obj_type = class(obj);
elseif ishandle(obj) && any(strcmpi(get(obj,'type'),{'axes' 'line' 'patch' 'surface' 'image' 'text'}))
valid_callbacks = {'ButtonDownFcn' 'CreateFcn' 'DeleteFcn'};
obj_type = get(obj,'type');
elseif ishandle(obj) && any(strcmpi(get(obj,'type'),'figure'))
valid_callbacks = {'ButtonDownFcn' 'CloseRequestFcn' 'CreateFcn' 'DeleteFcn' 'KeyPressFcn' 'KeyReleaseFcn' ...
'ResizeFcn' 'WindowButtonDownFcn' 'WindowButtonMotionFcn' 'WindowButtonUpFcn' 'WindowScrollWheelFcn' };
obj_type = 'figure';
end
cb_type = varargin{2};
if ~ischar(cb_type) || ~any(strcmpi(valid_callbacks,cb_type))
error([ sprintf('\n\tError in specifying callback for object of type ''%s''. Valid callbacks are ...\n\n\t',obj_type) ...
sprintf(' ''%s'' ',valid_callbacks{:})])
end
end
% All callbacks are casted into the form {@MyFcn, arg1, ... , argN}
callback = varargin{3};
if ischar(callback)
callback = regexp(callback,'\w*','match');
callback{1} = str2func(callback{1});
elseif isa(callback,'function_handle')
fcn_tmp = functions(callback);
if ~strcmp(fcn_tmp.type,'simple'); disp(sprintf('\n\tFunctions-handles of type "%s" may cause problems. Use at own risk.\n',fcn_tmp.type)); end
callback = {callback};
elseif iscell(callback) && isa(callback{1},'function_handle')
% This is ok
else
error('Third argument needs to be a valid function handle')
end
end