function id_out = iptaddcallback(h, callback, func_handle)
%IPTADDCALLBACK Add function handle to a callback list.
%   ID = IPTADDCALLBACK(H, CALLBACK, FUNC_HANDLE) adds the function
%   handle FUNC_HANDLE to the list of functions to be called when the
%   callback specified by CALLBACK executes. CALLBACK is a string
%   specifying the name of a callback property of the Handle Graphics
%   object specified by the handle H.
%
%   IPTADDCALLBACK returns a unique callback identifier, ID, that can 
%   be used with IPTREMOVECALLBACK to remove the function from the 
%   callback list.  
%
%   IPTADDCALLBACK can be useful when more than one tool want to be
%   notified about the same callback event for a single object. 
%
%   Note
%   ----
%   Callback functions that have already been added to an object 
%   using the SET command continue to work after calling IPTADDCALLBACK.
%   The first time you call IPTADDCALLBACK for a given object and 
%   callback, the function checks to see if a different callback 
%   function is already installed. If there is, IPTADDCALLBACK 
%   replaces that callback function with the IPTADDCALLBACK callback
%   processor, and then adds the pre-existing callback function
%   to the IPTADDCALLBACK list.
%
%   Example
%   -------
%       % Callbacks f1 and f2 are called for mouse motion over a
%       % figure. The functions are called in the order they
%       % are added to the list.
%       h = figure;
%       f1 = @(varargin) disp('Callback 1');
%       f2 = @(varargin) disp('Callback 2');
%       iptaddcallback(h, 'WindowButtonMotionFcn', f1);
%       iptaddcallback(h, 'WindowButtonMotionFcn', f2);
%   
%   See also IPTREMOVECALLBACK.

%   Copyright 1993-2004 The MathWorks, Inc.
%   $Revision: 1.1.8.1 $  $Date: 2004/08/10 01:49:22 $

iptchecknargin(3, 3, nargin, mfilename);
if (numel(h) ~= 1) || ~ishandle(h)
    error('Images:iptaddcallback:invalidHandle', ...
          'H must be a scalar handle.');
end

iptcheckinput(callback, {'char'}, {'row'}, mfilename, ...
              'CALLBACK', 2);
iptcheckinput(func_handle, {'function_handle'}, {'scalar'}, ...
              mfilename, 'FUNC_HANDLE', 3);

% State for imCallbackProcessor nested function.  There will be one of
% these callback lists for each H/CALLBACK combination.
callback_list = struct('func', {}, 'id', {});
next_available_id = 1;

% If the currently installed callback is not a function handle to
% imCallbackProcessor, then remember the currently installed callback,
% set the callback to @imCallbackProcessor, and then add the current
% callback to the callback list using a recursive call to iptaddcallback.
current_callback = get(h, callback);
if ~( isa(current_callback, 'function_handle') && ...
      strcmp(func2str(current_callback), 'iptaddcallback/callbackProcessor') )
    set(h, callback, @callbackProcessor);
    if ~isempty(current_callback)
        iptaddcallback(h, callback, current_callback);
    end
end

% Get the particular callbackProcessor function handle in use for this
% H/CALLBACK combination and use its 'add' syntax to add the new function
% handle to the callback list.
cpFun = get(h, callback);
id_out = cpFun('add', func_handle);

  function varargout = callbackProcessor(varargin)
  %   id = callbackProcessor('add', func_handle) adds the function handle
  %   to the callback list.
  %
  %   callbackProcessor('delete', id) deletes the callback with the
  %   associated identifier from the callback list.  id is the value
  %   returned by iptaddcallback.  If no matching callback is found,
  %   return silently.
  %
  %   list = callbackProcessor('list') returns the callback list.  This
  %   syntax is provided as a debugging and testing aid.
  %
  %   With any other form of input arguments, imCallbackProcessor invokes
  %   each entry in the callback list in turn, passing the input
  %   arguments along to them.
    
    if ischar(varargin{1}) && strcmp(varargin{1}, 'add')
        % Syntax: callbackProcessor('add', func_handle)
        callback_list(end+1).func = varargin{2};
        id = next_available_id;
        next_available_id = next_available_id + 1;
        callback_list(end).id = id;
        varargout{1} = id;
        
    elseif ischar(varargin{1}) && strcmp(varargin{1}, 'delete')
        % Syntax: callbackProcessor('delete', func_handle)
        id = varargin{2};
        for k = 1:numel(callback_list)
            if callback_list(k).id == id
                callback_list(k) = [];
                return;
            end
        end
    
    elseif ischar(varargin{1}) && strcmp(varargin{1}, 'list')
        % Syntax: list = imCallbackProcessor('list')
        varargout{1} = callback_list;
        
    else
        % All other syntaxes.
        for k = 1:numel(callback_list)
            fun = callback_list(k).func;
            if ischar(fun)
                eval(fun);
            else
                fun(varargin{:});
            end
        end
    end
    
  end

end
