function varargout = spm_XYZreg(varargin)
% Registry for GUI XYZ locations, and point list utility functions
%
%                            ----------------
%
% PointList & voxel centre utilities...
%
% FORMAT [xyz,d] = spm_XYZreg('RoundCoords',xyz,M,D)
% FORMAT [xyz,d] = spm_XYZreg('RoundCoords',xyz,V)
% Round specified xyz location to nearest voxel centre
% xyz - (Input) 3-vector of X, Y & Z locations, in "real" coordinates
% M   - 4x4 transformation matrix relating voxel to "real" coordinates
% D   - 3 vector of image X, Y & Z dimensions (DIM)
% V   - 9-vector of image and voxel sizes, and origin [DIM,VOX,ORIGIN]'
%       M derived as [ [diag(V(4:6)), -(V(7:9).*V(4:6))]; [zeros(1,3) ,1]]
%       DIM    - D
%       VOX    - Voxel dimensions in units of "real" coordinates
%       ORIGIN - Origin of "real" coordinates in voxel coordinates
% xyz - (Output) coordinates of nearest voxel centre in "real" coordinates
% d   - Euclidean distance between requested xyz & nearest voxel centre
%
% FORMAT i = spm_XYZreg('FindXYZ',xyz,XYZ)
% find position of specified voxel in XYZ pointlist
% xyz - 3-vector of coordinates
% XYZ - Pointlist: 3xn matrix of coordinates
% i   - Column(s) of XYZ equal to xyz
%
% FORMAT [xyz,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ)
% find nearest voxel in pointlist to specified location
% xyz - (Input) 3-vector of coordinates
% XYZ - Pointlist: 3xn matrix of coordinates
% xyz - (Output) coordinates of nearest voxel in XYZ pointlist
%       (ties are broken in favour of the first location in the pointlist)
% i   - Column of XYZ containing coordinates of nearest pointlist location
% d   - Euclidean distance between requested xyz & nearest pointlist location
%
% FORMAT d = spm_XYZreg('Edist',xyz,XYZ)
% Euclidean distances between coordinates xyz & points in XYZ pointlist
% xyz - 3-vector of coordinates
% XYZ - Pointlist: 3xn matrix of coordinates
% d   - n row-vector of Euclidean distances between xyz & points of XYZ
%
%                            ----------------
%
% Registry functions
%
% FORMAT [hReg,xyz] = spm_XYZreg('InitReg',hReg,M,D,xyz)
% Initialise registry in graphics object
% hReg - Handle of HandleGraphics object to build registry in. Object must
%        be un'Tag'ged and have empty 'UserData'
% M    - 4x4 transformation matrix relating voxel to "real" coordinates,
%        used and stored for checking validity of coordinates
% D    - 3 vector of image X, Y & Z dimensions (DIM), used
%        and stored for checking validity of coordinates
% xyz  - (Input) Initial coordinates [Default [0;0;0]]
%        These are rounded to the nearest voxel centre
% hReg - (Output) confirmation of registry handle
% xyz  - (Output) Current registry coordinates, after rounding
%
% FORMAT spm_XYZreg('UnInitReg',hReg)
% Clear registry information from graphics object
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object.
%        Object's 'Tag' & 'UserData' are cleared
%
% FORMAT xyz = spm_XYZreg('GetCoords',hReg)
% Get current registry coordinates
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
%
% FORMAT [xyz,d] = spm_XYZreg('SetCoords',xyz,hReg,hC,Reg)
% Set coordinates in registry & update registered HGobjects/functions
% xyz  - (Input) desired coordinates
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
%        If hReg doesn't contain a registry, a warning is printed.
% hC   - Handle of caller object (to prevent circularities) [Default 0]
%        If caller object passes invalid registry handle, then spm_XYZreg
%        attempts to blank the 'hReg' fiend of hC's 'UserData', printing
%        a warning notification.
% Reg  - Alternative nx2 cell array Registry of handles / functions
%        If specified, overrides use of registry held in hReg
%        [Default getfield(get(hReg,'UserData'),'Reg')]
% xyz  - (Output) Desired coordinates are rounded to nearest voxel if hC
%        is not specified, or is zero. Otherwise, caller is assummed to
%        have checked verity of desired xyz coordinates. Output xyz
%        returns coordinates actually set.
% d    - Euclidean distance between desired and set coordinates.
%
% FORMAT nReg = spm_XYZreg('XReg',hReg,{h,Fcn}pairs)
% Cross registration object/function pairs with the registry, push xyz coords
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
% h    - Handle of HandleGraphics object to be registered
%        The 'UserData' of h must be a structure with an 'Reg' field, which
%        is set to hReg, the handle of the registry (back registration)
% Fcn  - Handling function for HandleGraphics object h
%        This function *must* accept XYZ updates via the call:
%                feval(Fcn,'SetCoords',xyz,h,hReg)
%        and should *not* call back the registry with the update!
%        {h,Fcn} are appended to the registry (forward registration)
% nReg - New registry cell array: Handles are checked for validity before
%        entry. Invalid handles are omitted, generating a warning.
%
% FORMAT nReg = spm_XYZreg('Add2Reg',hReg,{h,Fcn}pairs)
% Add object/function pairs for XYZ updates to registry (forward registration)
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
% h    - Handle of HandleGraphics object to be registered
% Fcn  - Handling function for HandleGraphics object h
%        This function *must* accept XYZ updates via the call:
%                feval(Fcn,'SetCoords',xyz,h,hReg)
%        and should *not* call back the registry with the update!
%        {h,Fcn} are appended to the registry (forward registration)
% nReg - New registry cell array: Handles are checked for validity before
%        entry. Invalid handles are omitted, generating a warning.
%
% FORMAT spm_XYZreg('SetReg',h,hReg)
% Set registry field of object's UserData (back registration)
% h    - Handle of HandleGraphics object to be registered
%        The 'UserData' of h must be a structure with an 'Reg' field, which
%        is set to hReg, the handle of the registry (back registration)
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
%
% FORMAT nReg = spm_XYZreg('unXReg',hReg,hD1,hD2,hD3,...)
% Un-cross registration of HandleGraphics object hD
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
% hD?  - Handles of HandleGraphics object to be unregistered
%        The 'UserData' of hD must be a structure with a 'Reg' field, which
%        is set to empty (back un-registration)
% nReg - New registry cell array: Registry entries with handle entry hD are
%        removed from the registry (forward un-registration)
%        Handles not in the registry generate a warning
%
% FORMAT nReg = spm_XYZreg('Del2Reg',hReg,hD)
% Delete HandleGraphics object hD from registry (forward un-registration)
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
% hD?  - Handles of HandleGraphics object to be unregistered
% nReg - New registry cell array: Registry entries with handle entry hD are
%        removed from the registry. Handles not in registry generate a warning
%
% FORMAT spm_XYZreg('UnSetReg',h)
% Unset registry field of object's UserData (back un-registration)
% h - Handle of HandleGraphics object to be unregistered
%     The 'UserData' of hD must be a structure with a 'Reg' field, which
%     is set to empty (back un-registration)
%
% FORMAT spm_XYZreg('CleanReg',hReg)
% Clean invalid handles from registry
% hReg - Handle of 'hReg' 'Tag'ged registry HandleGraphics object
%
% FORMAT Reg = spm_XYZreg('VReg',Reg,Warn)
% Prune invalid handles from Registry cell array
% Reg  - (Input) nx2 cell array of {handle,function} pairs
% Warn - If specified, print warning if find invalid handles
% Reg  - (Output) mx2 cell array of valid {handle,function} pairs
%
% FORMAT hReg = spm_XYZreg('FindReg',h)
% Find/check registry object
% h    - handle of Registry, or figure containing Registry (default gcf)
%        If ischar(h), then uses spm_figure('FindWin',h) to locate named figures
% hReg - handle of confirmed registry object
%        Errors if h is not a registry or a figure containing a unique registry
%        Registry object is identified by 'hReg' 'Tag'
%__________________________________________________________________________
%
% spm_XYZreg provides a framework for modular inter-GUI communication of
% XYZ co-orginates, and various utility functions for pointlist handling
% and rounding in voxel coordinates.
% Concept and examples can be found in the body of the function.
%__________________________________________________________________________
% Copyright (C) 1999-2013 Wellcome Trust Centre for Neuroimaging

% Andrew Holmes, Chloe Hutton
% $Id: spm_XYZreg.m 5903 2014-03-03 14:56:10Z guillaume $



% THE REGISTRY
%
% The concept of the registry is of a central entity which "knows" about
% other GUI objects holding XYZ coordinates, and keeps them all in sync.
% Changes to the registry's XYZ coordinates are passed on to registered
% functions by the registry (forward registration). Individual objects
% which can change the XYZ coordinates should therefore update the
% registry with the new coordinates (back registration), so that the
% registry can tell all registered objects about the new location, and a
% framework is provided for this.
%
% The registry is held as the 'UserData of a HandleGraphics object, whose
% handle therefore identifies the registry. The registry object is 'Tag'ged
% 'hReg' for identification (though this 'Tag' is not used for locating the
% registry, so multiple registry incarnations are possible). The registry
% object's 'UserData' is a structure containing the current XYZ
% coordinates, the voxel-to-coordinates matrix M, the image dimensions D,
% and the Registry itself. The registry is a nx2 cell array containing n
% handle/function pairs.
%
% The model is that all GUI objects requiring linking to a common XYZ
% location via the registry each be identified by a HandleGraphics handle.
% This handle can be the handle of the particular instantiation of the GUI
% control itself (as is the case with the MIP-GUI of spm_mip_ui where the
% axis handle is used to identify the MIP to use); the handle of another
% HandleGraphics object associated with the GUI control (as is the case
% with the XYZ editable widgets of spm_results_ui where the handle of the
% bounding frame uicontrol is used); or may be 0, the handle of the root
% object, which allows non GUI functions (such as a function that just
% prints information) to be added to the registry. The registry itself thus
% conforms to this model. Each object has an associated "handling function"
% (so this function is the registry's handling function). The registry
% itself consists of object-handle/handling-function pairs.
%
% If an object and it's handling function are entered in the registry, then
% the object is said to be "forward registered", because the registry will
% now forward all location updates to that object, via it's handling
% function. The assummed syntax is: feval(Fcn,'SetCoords',xyz,h,hReg),
% where Fcn is the handling function for the GUI control identified by
% handle h, xyz are the new coordinates, and hReg is the handle of the
% registry.
%
% An optional extension is "back registration", whereby the GUI controls
% inform the registry of the new location when they are updated. All that's
% required is that the objects call the registry's 'SetCoords' function:
% spm_XYZreg('SetCoords',xyz,hReg,hC), where hReg is the registry object's
% handle, and hC is the handle associated with the calling GUI control. The
% specification of the caller GUI control allows the registry to avoid
% circularities: If the object is "forward registered" for updates, then
% the registry function doesn't try to update the object which just updated
% the registry! (Similarly, the handle of the registry object, hReg, is
% passed to the handling function during forward XYZ updating, so that the
% handling function's 'SetCoords' facility can be constructed to accept XYZ
% updates from various sources, and only inform the registry if not called
% by the registry, and hence avoid circularities.)
%
% A framework is provided for "back" registration. Really all that is
% required is that the GUI controls know of the registry object (via it's
% handle hReg), and call the registry's 'SetCoords' facility when
% necessary. This can be done in many ways, but a simple structure is
% provided, mirroring that of the registry's operation. This framework
% assummes that the GUI controls identification object's 'UserData' is a
% structure with a field named 'hReg', which stores the handle of the
% registry (if back registered), or is empty (if not back registered, i.e.
% standalone). spm_XYZreg provides utility functions for setting/unsetting
% this field, and for "cross registering" - that is both forward and back
% registration in one command. Cross registering involves adding the
% handle/function pair to the registry, and setting the registry handle in
% the GUI control object's 'UserData' 'hReg' field. It's up to the handling
% function to read the registry handle from it's objects 'UserData' and act
% accordingly. A simple example of such a function is provided in
% spm_XYZreg_Ex2.m, illustrated below.
%
% SubFunctions are provided for getting and setting the current
% coordinates; adding and deleting handle/function pairs from the registry
% (forward registration and un-registration), setting and removing registry
% handle information from the 'hReg' field of the 'UserData' of a HG object
% (backward registration & un-registration); cross registration and
% unregistration (including pushing of current coordinates); setting and
% getting the current XYZ location. See the FORMAT statements and the
% example below...
%
%                            ----------------
% EXAMPLE
%
% %-Create a window:
% F = figure;
% %-Create an object to hold the registry
% hReg = uicontrol(F,'Style','Text','String','hReg',...
%   'Position',[100 200 100 025],...
%   'FontName','Times','FontSize',14,'FontWeight','Bold',...
%   'HorizontalAlignment','Center');
% %-Setup M & D
% V = [65;87;26;02;02;04;33;53;08];
% M = [ [diag(V(4:6)), -(V(7:9).*V(4:6))]; [zeros(1,3) ,1]];
% D = V(1:3);
% %-Initialise a registry in this object, with initial coordinates [0;0;0]
% spm_XYZreg('InitReg',hReg,M,D,[0;0;0])
% % (ans returns [0;0;0] confirming current coordinates
% %-Set coordinates to [10;10;10]
% spm_XYZreg('SetCoords',[10,10,10],hReg)
% % (warns of coordinate rounding to [10,10,12], & returns ans as [10;10;12])
%
% %-Forward register a command window xyz reporting function: spm_XYZreg_Ex1.m
% spm_XYZreg('Add2Reg',hReg,0,'spm_XYZreg_Ex1')
% % (ans returns new registry, containing just this handle/function pair
% %-Set coordinates to [0;10;12]
% [xyz,d] = spm_XYZreg('SetCoords',[0,10,12],hReg);
% % (spm_XYZreg_Ex1 called, and prints coordinates and handles)
% %-Have a peek at the registry information
% RD = get(hReg,'UserData')
% RD.xyz    %-The current point according to the registry
% RD.Reg    %-The nx2 cell array of handle/function pairs
%
% %-Create an example GUI XYZ control, using spm_XYZreg_Ex2.m
% hB = spm_XYZreg_Ex2('Create',M,D,xyz);
% % (A figure window with a button appears, whose label shows the current xyz
% %-Press the button, and enter new coordinates [0;0;0] in the Cmd window...
% % (...the button's internal notion of the current location is changed, but
% % (the registry isn't informed:
% spm_XYZreg('GetCoords',hReg)
% (...returns [0;10;12])
% %-"Back" register the button
% spm_XYZreg('SetReg',hB,hReg)
% %-Check the back registration
% if ( hReg == getfield(get(hB,'UserData'),'hReg') ), disp('yes!'), end
% %-Now press the button, and enter [0;0;0] again...
% % (...this time the registry is told, and the registry tells spm_XYZreg_Ex1,
% % (which prints out the new coordinates!
% %-Forward register the button to receive updates from the registry
% nReg = spm_XYZreg('Add2Reg',hReg,hB,'spm_XYZreg_Ex2')
% % (The new registry is returned as nReg, showing two entries
% %-Set new registry coordinates to [10;10;12]
% [xyz,d] = spm_XYZreg('SetCoords',[10;10;12],hReg);
% % (...the button updates too!
%
% %-Illustration of robustness: Delete the button & use the registry
% delete(hB)
% [xyz,d] = spm_XYZreg('SetCoords',[10;10;12],hReg);
% % (...the invalid handle hB in the registry is ignored)
% %-Peek at the registry
% getfield(get(hReg,'UserData'),'Reg')
% %-Delete hB from the registry by "cleaning"
% spm_XYZreg('CleanReg',hReg)
% % (...it's gone
%
% %-Make a new button and cross register
% hB = spm_XYZreg_Ex2('Create',M,D)
% % (button created with default coordinates of [0;0;0]
% nReg = spm_XYZreg('XReg',hReg,hB,'spm_XYZreg_Ex2')
% % (Note that the registry pushes the current coordinates to the button
% %-Use the button & spm_XYZreg('SetCoords'... at will!



%==========================================================================
switch lower(varargin{1}), case 'roundcoords'
%==========================================================================
% [xyz,d] = spm_XYZreg('RoundCoords',xyz,M,D)
% [xyz,d] = spm_XYZreg('RoundCoords',xyz,V)
if nargin<3, error('Insufficient arguments'), end
if nargin<4
    V = varargin{3};
    M = [ [diag(V(4:6)), -(V(7:9).*V(4:6))]; [zeros(1,3) ,1]];
    D = V(1:3);
else
    M = varargin{3};
    D = varargin{4};
end

%-Round xyz to coordinates of actual voxel centre
%-Do rounding in voxel coordinates & ensure within image size
%-Watch out for infinities!
%--------------------------------------------------------------------------
xyz  = [varargin{2}(:); 1];
xyz(isinf(xyz)) = 1e10*sign(xyz(isinf(xyz)));
rcp  = round(M\xyz);
rcp  = max([min([rcp';[D',1]]);[1,1,1,1]])';
rxyz = M*rcp;

%-Work out Euclidean distance between points xyz & rounded xyz
d = sqrt(sum((xyz-rxyz).^2));

varargout = {rxyz(1:3),d};



%==========================================================================
case 'findxyz'
%==========================================================================
% i = spm_XYZreg('FindXYZ',xyz,XYZ)
if nargin<3, error('Insufficient arguments'), end
XYZ = varargin{3};
xyz = varargin{2};

%-Find XYZ = xyz
%--------------------------------------------------------------------------
i = find(all([XYZ(1,:)==xyz(1);XYZ(2,:)==xyz(2);XYZ(3,:)==xyz(3)],1));

varargout = {i};



%==========================================================================
case 'nearestxyz'
%==========================================================================
% [xyz,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ)
if nargin<3, error('Insufficient arguments'), end

%-Find in XYZ nearest point to coordinates xyz (Euclidean distance)
%--------------------------------------------------------------------------
[d,i] = min(spm_XYZreg('Edist',varargin{2},varargin{3}));
varargout = {varargin{3}(:,i),i,d};



%==========================================================================
case 'edist'
%==========================================================================
% d = spm_XYZreg('Edist',xyz,XYZ)
if nargin<3, error('Insufficient arguments'), end

%-Calculate (Euclidean) distances from pointlist coords to xyz
%--------------------------------------------------------------------------
varargout = {sqrt(sum([ (varargin{3}(1,:) - varargin{2}(1));...
    (varargin{3}(2,:) - varargin{2}(2));...
    (varargin{3}(3,:) - varargin{2}(3)) ].^2))};



%==========================================================================
case 'initreg'                            % Initialise registry in handle h
%==========================================================================
% [hReg,xyz] = spm_XYZreg('InitReg',hReg,M,D,xyz)
if nargin<5, xyz=[0;0;0]; else xyz=varargin{5}; end
if nargin<4, error('Insufficient arguments'), end
D    = varargin{4};
M    = varargin{3};
hReg = varargin{2};

%-Check availability of hReg object for building a registry in
%--------------------------------------------------------------------------
if ~isempty(get(hReg,'UserData')), error('Object already has UserData...'), end
if ~isempty(get(hReg,'Tag')), error('Object already ''Tag''ed...'), end

%-Check coordinates are in range
%--------------------------------------------------------------------------
[xyz,d] = spm_XYZreg('RoundCoords',xyz,M,D);
if d>0 && nargout<2
    warning('Coordinates rounded to nearest voxel center: Discrepancy %.2f',d);
end

%-Set up registry
%--------------------------------------------------------------------------
RD = struct('xyz',xyz,'M',M,'D',D,'Reg',[]);
RD.Reg = {};
set(hReg,'Tag','hReg','UserData',RD)

%-Return current coordinates
%--------------------------------------------------------------------------
varargout = {hReg,xyz};



%==========================================================================
case 'uninitreg'                     % UnInitialise registry in handle hReg
%==========================================================================
% spm_XYZreg('UnInitReg',hReg)
hReg = varargin{2};
if ~strcmp(get(hReg,'Tag'),'hReg'), warning('Not an XYZ registry'), return, end
set(hReg,'Tag','','UserData',[])



%==========================================================================
case 'getcoords'                                  % Get current coordinates
 %=========================================================================
% xyz = spm_XYZreg('GetCoords',hReg)
if nargin<2, hReg=spm_XYZreg('FindReg'); else hReg=varargin{2}; end
if ~ishandle(hReg), error('Invalid object handle'), end
if ~strcmp(get(hReg,'Tag'),'hReg'), error('Not a registry'), end
varargout = {getfield(get(hReg,'UserData'),'xyz')};



%==========================================================================
case 'setcoords'            % Set coordinates & update registered functions
%==========================================================================
% [xyz,d] = spm_XYZreg('SetCoords',xyz,hReg,hC,Reg)
% d returned empty if didn't check, warning printed if d not asked for & round
% Don't check if callerhandle specified (speed)
% If Registry cell array Reg is specified, then only these handles are updated
hC=0; mfn='';
if nargin>=4
    if ~ischar(varargin{4}), hC=varargin{4}; else mfn=varargin{4}; end
end
hReg = varargin{3};

%-Check validity of hReg registry handle
%--------------------------------------------------------------------------
%-Return if hReg empty, in case calling objects functions don't check isempty
if isempty(hReg), return, end
%-Check validity of hReg registry handle, correct calling objects if necc.
if ~ishandle(hReg)
    str = sprintf('%s: Invalid registry handle (%.4f)',mfilename,hReg);
    if hC>0
        %-Remove hReg from caller
        spm_XYZreg('SetReg',hC,[])
        str = [str,sprintf('\n\t\t\t...removed from caller (%.4f)',hC)];
    end
    warning(str)
    return
end
xyz = varargin{2};

RD  = get(hReg,'UserData');

%-Check validity of coords only when called without a caller handle
%--------------------------------------------------------------------------
if hC<=0
    [xyz,d] = spm_XYZreg('RoundCoords',xyz,RD.M,RD.D);
    if d>0 && nargout<2
        warning('Coordinates rounded to nearest voxel center: Discrepancy %.2f',d);
    end
else
    d = 0;
end

%-Sort out valid handles, eliminate caller handle, update coords with
% registered handles via their functions
%--------------------------------------------------------------------------
if nargin<5
    RD.Reg = spm_XYZreg('VReg',RD.Reg);
    Reg    = RD.Reg;
else
    Reg = spm_XYZreg('VReg',varargin{5});
end
if hC>0 && ~isempty(Reg), Reg([Reg{:,1}]==varargin{4},:) = []; end
for i = 1:size(Reg,1)
    feval(Reg{i,2},'SetCoords',xyz,Reg{i,1},hReg);
end

%-Update registry (if using hReg) with location & cleaned Reg cellarray
%--------------------------------------------------------------------------
if nargin<5
    RD.xyz  = xyz;
    set(hReg,'UserData',RD)
end

varargout = {xyz,d};



%==========================================================================
case 'xreg'                     % Cross register object handles & functions
 %=========================================================================
% nReg = spm_XYZreg('XReg',hReg,{h,Fcn}pairs)
if nargin<4, error('Insufficient arguments'), end
hReg = varargin{2};

%-Quick check of registry handle
%--------------------------------------------------------------------------
if isempty(hReg),   warning('Empty registry handle'), return, end
if ~ishandle(hReg), warning('Invalid registry handle'), return, end

%-Condition nReg cell array & check validity of handles to be registered
%--------------------------------------------------------------------------
nReg = varargin(3:end);
if mod(length(nReg),2), error('Registry items must be in pairs'), end
if length(nReg)>2, nReg = reshape(nReg,length(nReg)/2,2)'; end
nReg = spm_XYZreg('VReg',nReg,'Warn');

%-Set hReg registry link for registry candidates (Back registration)
%--------------------------------------------------------------------------
for i = 1:size(nReg,1)
    spm_XYZreg('SetReg',nReg{i,1},hReg);
end

%-Append registry candidates to existing registry & write back to hReg
%--------------------------------------------------------------------------
RD     = get(hReg,'UserData');
Reg    = RD.Reg;
Reg    = cat(1,Reg,nReg);
RD.Reg = Reg;
set(hReg,'UserData',RD)

%-Synch coordinates of newly registered objects
%--------------------------------------------------------------------------
spm_XYZreg('SetCoords',RD.xyz,hReg,hReg,nReg);

varargout = {Reg};



%==========================================================================
case 'add2reg'                    % Add handle(s) & function(s) to registry
%==========================================================================
% nReg = spm_XYZreg('Add2Reg',hReg,{h,Fcn}pairs)
if nargin<4, error('Insufficient arguments'), end
hReg = varargin{2};

%-Quick check of registry handle
%--------------------------------------------------------------------------
if isempty(hReg),   warning('Empty registry handle'), return, end
if ~ishandle(hReg), warning('Invalid registry handle'), return, end

%-Condition nReg cell array & check validity of handles to be registered
%--------------------------------------------------------------------------
nReg = varargin(3:end);
if mod(length(nReg),2), error('Registry items must be in pairs'), end
if length(nReg)>2, nReg = reshape(nReg,length(nReg)/2,2)'; end
nReg = spm_XYZreg('VReg',nReg,'Warn');

%-Append to existing registry & put back in registry object
%--------------------------------------------------------------------------
RD     = get(hReg,'UserData');
Reg    = RD.Reg;
Reg    = cat(1,Reg,nReg);
RD.Reg = Reg;
set(hReg,'UserData',RD)

varargout = {Reg};



%==========================================================================
case 'setreg'                     %-Set registry field of object's UserData
%==========================================================================
% spm_XYZreg('SetReg',h,hReg)
if nargin<3, error('Insufficient arguments'), end
h    = varargin{2};
hReg = varargin{3};
if ( ~ishandle(h) || h==0 ), return, end
UD = get(h,'UserData');
if ~isstruct(UD) || ~any(strcmp(fieldnames(UD),'hReg'))
    error('No UserData structure with hReg field for this object')
end
UD.hReg = hReg;
set(h,'UserData',UD)



%==========================================================================
case 'unxreg'                % Un-cross register object handles & functions
%==========================================================================
% nReg = spm_XYZreg('unXReg',hReg,hD1,hD2,hD3,...)
if nargin<3, error('Insufficient arguments'), end
hD       = [varargin{3:end}];
hReg     = varargin{2};

%-Get Registry information
%--------------------------------------------------------------------------
RD       = get(hReg,'UserData');
Reg      = RD.Reg;

%-Find registry entires to delete
%--------------------------------------------------------------------------
[c,i,e]  = intersect([Reg{:,1}],hD);
hD(e)    = [];
dReg     = spm_XYZreg('VReg',Reg(i,:));
Reg(i,:) = [];
if ~isempty(hD), warning('Not all handles were in registry'), end

%-Write back new registry
%--------------------------------------------------------------------------
RD.Reg   = Reg;
set(hReg,'UserData',RD)

%-UnSet hReg registry link for hD's still existing (Back un-registration)
%--------------------------------------------------------------------------
for i = 1:size(dReg,1)
    spm_XYZreg('SetReg',dReg{i,1},[]);
end

varargout = {Reg};



%==========================================================================
case 'del2reg'               % Delete handle(s) & function(s) from registry
%==========================================================================
% nReg = spm_XYZreg('Del2Reg',hReg,hD)
if nargin<3, error('Insufficient arguments'), end
hD       = [varargin{3:end}];
hReg     = varargin{2};

%-Get Registry information
%--------------------------------------------------------------------------
RD       = get(hReg,'UserData');
Reg      = RD.Reg;

%-Find registry entires to delete
%--------------------------------------------------------------------------
[c,i,e]  = intersect([Reg{:,1}],hD);
Reg(i,:) = [];
hD(e)    = [];
if ~isempty(hD), warning('Not all handles were in registry'), end

%-Write back new registry
%--------------------------------------------------------------------------
RD.Reg = Reg;
set(hReg,'UserData',RD)

varargout = {Reg};



%==========================================================================
case 'unsetreg'                 %-Unset registry field of object's UserData
%==========================================================================
% spm_XYZreg('UnSetReg',h)
if nargin<2, error('Insufficient arguments'), end
spm_XYZreg('SetReg',varargin{2},[])



%==========================================================================
case 'cleanreg'                       % Clean invalid handles from registry
%==========================================================================
% spm_XYZreg('CleanReg',hReg)
%if ~strcmp(get(hReg,'Tag'),'hReg'), error('Not a registry'), end
hReg = varargin{2};
RD = get(hReg,'UserData');
RD.Reg = spm_XYZreg('VReg',RD.Reg,'Warn');
set(hReg,'UserData',RD)



%==========================================================================
case 'vreg'                % Prune invalid handles from registry cell array
%==========================================================================
% Reg = spm_XYZreg('VReg',Reg,Warn)
if nargin<3, Warn=0; else Warn=1; end
Reg = varargin{2};
if isempty(Reg), varargout={Reg}; return, end
i = [];
for h=1:size(Reg,1)
    if ~ishghandle(Reg{h,1}), i = [i h]; end
end
%-***check existence of handling functions : exist('','file')?
if Warn && ~isempty(i)
    warning('Disregarding invalid registry handles:\n\t%.4f',Reg{i,1});
end
Reg(i,:)  = [];
varargout = {Reg};



%==========================================================================
case 'findreg'          % Find/check registry object
%==========================================================================
% hReg = spm_XYZreg('FindReg',h)
if nargin<2, h=get(0,'CurrentFigure'); else h=varargin{2}; end
if ischar(h), h=spm_figure('FindWin',h); end
if ~ishandle(h), error('invalid handle'), end
if ~strcmp(get(h,'Tag'),'hReg'), h=findobj(h,'Tag','hReg'); end
if isempty(h), error('Registry object not found'), end
if length(h)>1, error('Multiple registry objects found'), end
varargout = {h};



%==========================================================================
otherwise
%==========================================================================
error('Unknown action string')

%==========================================================================
end
