function [t,sts] = cfg_getfile(varargin)
% File selector
% FORMAT [t,sts] = cfg_getfile(n,typ,mesg,sel,wd,filt,frames)
%     n    - Number of files
%            A single value or a range.  e.g.
%            1       - Select one file
%            Inf     - Select any number of files
%            [1 Inf] - Select 1 to Inf files
%            [0 1]   - select 0 or 1 files
%            [10 12] - select from 10 to 12 files
%     typ  - file type
%           'any'   - all files
%           'batch' - SPM batch files (.m, .mat and XML)
%           'dir'   - select a directory
%           'image' - Image files (".img" and ".nii")
%                     Note that it gives the option to select
%                     individual volumes of the images.
%           'mat'   - Matlab .mat files or .txt files (assumed to contain
%                     ASCII representation of a 2D-numeric array)
%           'mesh'  - Mesh files (".gii" and ".mat")
%           'nifti' - NIfTI files without the option to select frames
%           'xml'   - XML files
%           Other strings act as a filter to regexp.  This means
%           that e.g. DCM*.mat files should have a typ of '^DCM.*\.mat$'
%      mesg - a prompt (default 'Select files...')
%      sel  - list of already selected files
%      wd   - Directory to start off in
%      filt - value for user-editable filter (default '.*')
%      frames - Image frame numbers to include (default '1')
%
%      t    - selected files
%      sts  - status (1 means OK, 0 means window quit)
%
% FORMAT [t,ind] = cfg_getfile('Filter',files,typ,filt,frames)
% filter the list of files (cell array) in the same way as the
% GUI would do. There is an additional typ 'extimage' which will match
% images with frame specifications, too. The 'frames' argument
% is currently ignored, i.e. image files will not be filtered out if
% their frame numbers do not match.
% When filtering directory names, the filt argument will be applied to the
% last directory in a path only.
% t returns the filtered list (cell array), ind an index array, such that
% t = files(ind).
%
% FORMAT cpath = cfg_getfile('CPath',path,cwd)
% function to canonicalise paths: Prepends cwd to relative paths, processes
% '..' & '.' directories embedded in path.
% path     - string matrix containing path name
% cwd      - current working directory [default '.']
% cpath    - conditioned paths, in same format as input path argument
%
% FORMAT [files,dirs]=cfg_getfile('List',direc,filt)
% Returns files matching the filter (filt) and directories within dire
% direc    - directory to search
% filt     - filter to select files with (see regexp) e.g. '^w.*\.img$'
% files    - files matching 'filt' in directory 'direc'
% dirs     - subdirectories of 'direc'
% FORMAT [files,dirs]=cfg_getfile('ExtList',direc,filt,frames)
% As above, but for selecting frames of 4D NIfTI files
% frames   - vector of frames to select (defaults to 1, if not
%            specified). If the frame number is Inf, all frames for the
%            matching images are listed.
% FORMAT [files,dirs]=cfg_getfile('FPList',direc,filt)
% FORMAT [files,dirs]=cfg_getfile('ExtFPList',direc,filt,frames)
% As above, but returns files with full paths (i.e. prefixes direc to each)
% FORMAT [files,dirs]=cfg_getfile('FPListRec',direc,filt)
% FORMAT [files,dirs]=cfg_getfile('ExtFPListRec',direc,filt,frames)
% As above, but returns files with full paths (i.e. prefixes direc to
% each) and searches through sub directories recursively.
%
% FORMAT cfg_getfile('prevdirs',dir)
% Add directory dir to list of previous directories.
% FORMAT dirs=cfg_getfile('prevdirs')
% Retrieve list of previous directories.
%
% This code is based on the file selection dialog in SPM5, with virtual
% file handling turned off.
%____________________________________________________________________________
% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience
%
% This code is part of a batch job configuration system for MATLAB. See
%      help matlabbatch
% for a general overview.
%_______________________________________________________________________
% Copyright (C) 2007 Freiburg Brain Imaging

% John Ashburner and Volkmar Glauche
% $Id: cfg_getfile.m 6251 2014-10-30 13:58:44Z volkmar $

t = {};
sts = false;
if nargin > 0 && ischar(varargin{1})
    switch lower(varargin{1})
        case {'addvfiles', 'clearvfiles', 'vfiles'}
            cfg_message('matlabbatch:deprecated:vfiles', ...
                        'Trying to use deprecated ''%s'' call.', ...
                        lower(varargin{1}));
        case 'cpath'
            cfg_message(nargchk(2,Inf,nargin,'struct'));
            t = cpath(varargin{2:end});
            sts = true;
        case 'filter'
            filt    = mk_filter(varargin{3:end});
            t       = varargin{2};
            if numel(t) == 1 && isempty(t{1})
                sts = 1;
                return;
            end;
            t1 = cell(size(t));
            if any(strcmpi(varargin{3},{'dir','extdir'}))
                % only filter last directory in path
                for k = 1:numel(t)
                    t{k} = cpath(t{k});
                    if t{k}(end) == filesep
                        [p n] = fileparts(t{k}(1:end-1));
                    else
                        [p n] = fileparts(t{k});
                    end
                    if strcmpi(varargin{3},'extdir')
                        t1{k} = [n filesep];
                    else
                        t1{k} = n;
                    end
                end
            else
                % only filter filenames, not paths
                for k = 1:numel(t)
                    [p n e] = fileparts(t{k});
                    t1{k} = [n e];
                end
            end
            [t1,sts1] = do_filter(t1,filt.ext);
            [t1,sts2] = do_filter(t1,filt.filt);
            sts = sts1(sts2);
            t = t(sts);
        case {'list', 'fplist', 'extlist', 'extfplist'}
            if nargin > 3
                frames = varargin{4};
            else
                frames = 1; % (ignored in listfiles if typ==any)
            end;
            if regexpi(varargin{1}, 'ext') % use frames descriptor
                typ = 'extimage';
            else
                typ = 'any';
            end
            filt    = mk_filter(typ, varargin{3}, frames);
            [t sts] = listfiles(varargin{2}, filt); % (sts is subdirs here)
            sts = sts(~(strcmp(sts,'.')|strcmp(sts,'..'))); % remove '.' and '..' entries
            if regexpi(varargin{1}, 'fplist') % return full pathnames
                direc = cfg_getfile('cpath', varargin{2});
                % remove trailing path separator if present
                direc = regexprep(direc, [filesep '$'], '');
                if ~isempty(t)
                    t = strcat(direc, filesep, t);
                end
                if nargout > 1
                    % subdirs too
                    sts = cellfun(@(sts1)cpath(sts1, direc), sts, 'UniformOutput',false);
                end
            end
        case {'fplistrec', 'extfplistrec'}
            % list directory
            [f1 d1] = cfg_getfile(varargin{1}(1:end-3),varargin{2:end});
            f2 = cell(size(d1));
            d2 = cell(size(d1));
            for k = 1:numel(d1)
                % recurse into sub directories
                [f2{k} d2{k}] = cfg_getfile(varargin{1}, d1{k}, ...
                                            varargin{3:end});
            end
            t = vertcat(f1, f2{:});
            if nargout > 1
                sts = vertcat(d1, d2{:});
            end
        case 'prevdirs',
            if nargin > 1
                prevdirs(varargin{2});
            end;
            if nargout > 0 || nargin == 1
                t = prevdirs;
                sts = true;
            end;
        otherwise
            cfg_message('matlabbatch:usage','Inappropriate usage.');
    end
else
    [t,sts] = selector(varargin{:});
end
%=======================================================================

%=======================================================================
function [t,ok] = selector(n,typ,mesg,already,wd,filt,frames,varargin)
if nargin<1 || ~isnumeric(n) || numel(n) > 2
    n = [0 Inf];
else
    if numel(n)==1,   n    = [n n];    end;
    if n(1)>n(2),     n    = n([2 1]); end;
    if ~isfinite(n(1)), n(1) = 0;        end;
end

if nargin<2 || ~ischar(typ), typ     = 'any';   end;

if nargin<3 || ~(ischar(mesg) || iscellstr(mesg))
    mesg    = 'Select files...';
elseif iscellstr(mesg)
    mesg = char(mesg);
end

if nargin<4 || isempty(already) || (iscell(already) && isempty(already{1}))
    already = {};
else
    % Add folders of already selected files to prevdirs list
    pd1 = cellfun(@(a1)strcat(fileparts(a1),filesep), already, ...
                  'UniformOutput',false);
    prevdirs(pd1);
end

if nargin<5 || isempty(wd) || ~ischar(wd)
    if isempty(already)
        wd      = pwd;
    else
        wd = fileparts(already{1});
        if isempty(wd)
            wd = pwd;
        end
    end;
end

if nargin<6 || ~ischar(filt), filt    = '.*';    end;

if nargin<7 || ~(isnumeric(frames) || ischar(frames))
    frames  = '1';
elseif isnumeric(frames)
    frames = char(gencode_rvalue(frames(:)'));
elseif ischar(frames)
    try
        ev = eval(frames);
        if ~isnumeric(ev)
            frames = '1';
        end
    catch
        frames = '1';
    end
end
ok  = 0;

t = '';
sfilt = mk_filter(typ,filt,eval(frames));

[col1,col2,col3,lf,bf] = colours;

% delete old selector, if any
fg = findobj(0,'Tag',mfilename);
if ~isempty(fg)
    delete(fg);
end
% create figure
fg = figure('IntegerHandle','off',...
    'Tag',mfilename,...
    'Name',mesg,...
    'NumberTitle','off',...
    'Units','Pixels',...
    'MenuBar','none',...
    'DefaultTextInterpreter','none',...
    'DefaultUicontrolInterruptible','on',...
    'Visible','off');

cfg_onscreen(fg);
set(fg,'Visible','on');

sellines = min([max([n(2) numel(already)]), 4]);
[pselp pcntp pfdp pdirp] = panelpositions(fg, sellines+1);

uicontrol(fg,...
          'style','text',...
          'units','normalized',...
          'position',posinpanel([0 sellines/(sellines+1) 1 1/(sellines+1)],pselp),...
          lf,...
          'BackgroundColor',get(fg,'Color'),...
          'ForegroundColor',col3,...
          'HorizontalAlignment','left',...
          'string',mesg,...
          'tag','msg');

% Selected Files
sel = uicontrol(fg,...
    'style','listbox',...
    'units','normalized',...
    'Position',posinpanel([0 0 1 sellines/(sellines+1)],pselp),...
    lf,...
    'Callback',@unselect,...
    'tag','selected',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'Max',10000,...
    'Min',0,...
    'String',already,...
    'Value',1);
c0 = uicontextmenu('Parent',fg);
set(sel,'uicontextmenu',c0);
uimenu('Label','Unselect All', 'Parent',c0,'Callback',@unselect_all);

% get cwidth for buttons
tmp=uicontrol('style','text','string',repmat('X',[1,50]),bf,...
    'units','normalized','visible','off');
fnp = get(tmp,'extent');
delete(tmp);
cw = 3*fnp(3)/50;

if strcmpi(typ,'image'),
    uicontrol(fg,...
        'style','edit',...
        'units','normalized',...
        'Position',posinpanel([0.61 0 0.37 .45],pcntp),...
        'Callback',@update_frames,...
        'tag','frame',...
        lf,...
        'BackgroundColor',col1,...
        'String',frames,'UserData',eval(frames));
% 'ForegroundGolor',col3,...
end;

% Help
uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',posinpanel([0.02 .5 cw .45],pcntp),...
    bf,...
    'Callback',@heelp,...
    'tag','?',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','?',...
    'ToolTipString','Show Help');

uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',posinpanel([0.03+cw .5 cw .45],pcntp),...
    bf,...
    'Callback',@editwin,...
    'tag','Ed',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','Ed',...
    'ToolTipString','Edit Selected Files');

uicontrol(fg,...
   'Style','pushbutton',...
   'units','normalized',...
   'Position',posinpanel([0.04+2*cw .5 cw .45],pcntp),...
   bf,...
   'Callback',@select_rec,...
   'tag','Rec',...
   'ForegroundColor',col3,...
   'BackgroundColor',col1,...
   'String','Rec',...
   'ToolTipString','Recursively Select Files with Current Filter');

% Done
dne = uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',posinpanel([0.05+3*cw .5 0.45-3*cw .45],pcntp),...
    bf,...
    'Callback',@(h,e)delete(h),...
    'tag','D',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','Done',...
    'Enable','off',...
    'DeleteFcn',@null);

if numel(already)>=n(1) && numel(already)<=n(2),
    set(dne,'Enable','on');
end;

% Filter Button
uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',posinpanel([0.51 .5 0.1 .45],pcntp),...
    bf,...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'Callback',@clearfilt,...
    'String','Filt');

% Filter
uicontrol(fg,...
    'style','edit',...
    'units','normalized',...
    'Position',posinpanel([0.61 .5 0.37 .45],pcntp),...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    lf,...
    'Callback',@(ob,ev)update(ob),...
    'tag','regexp',...
    'String',filt,...
    'UserData',sfilt);

% Directories
db = uicontrol(fg,...
    'style','listbox',...
    'units','normalized',...
    'Position',posinpanel([0.02 0 0.47 1],pfdp),...
    lf,...
    'Callback',@click_dir_box,...
    'tag','dirs',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'Max',1,...
    'Min',0,...
    'String','',...
    'UserData',wd,...
    'Value',1);

% Files
tmp = uicontrol(fg,...
    'style','listbox',...
    'units','normalized',...
    'Position',posinpanel([0.51 0 0.47 1],pfdp),...
    lf,...
    'Callback',@click_file_box,...
    'tag','files',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'UserData',n,...
    'Max',10240,...
    'Min',0,...
    'String','',...
    'Value',1);
c0 = uicontextmenu('Parent',fg);
set(tmp,'uicontextmenu',c0);
uimenu('Label','Select All', 'Parent',c0,'Callback',@select_all);

% Drives
if strcmpi(computer,'PCWIN') || strcmpi(computer,'PCWIN64'),
    % get fh for lists
    tmp=uicontrol('style','text','string','X',lf,...
        'units','normalized','visible','off');
    fnp = get(tmp,'extent');
    delete(tmp);
    fh = 2*fnp(4); % Heuristics: why do we need 2*
    sz = get(db,'Position');
    sz(4) = sz(4)-fh-2*0.01;
    set(db,'Position',sz);
    uicontrol(fg,...
        'style','text',...
        'units','normalized',...
        'Position',posinpanel([0.02 1-fh-0.01 0.10 fh],pfdp),...
              'HorizontalAlignment','left',...
        lf,...
        'BackgroundColor',get(fg,'Color'),...
        'ForegroundColor',col3,...
        'String','Drive');
    uicontrol(fg,...
        'style','popupmenu',...
        'units','normalized',...
        'Position',posinpanel([0.12 1-fh-0.01 0.37 fh],pfdp),...
        lf,...
        'Callback',@setdrive,...
        'tag','drive',...
        'BackgroundColor',col1,...
        'ForegroundColor',col3,...
        'String',listdrives(false),...
        'Value',1);
end;

[pd,vl] = prevdirs([wd filesep]);

% Previous dirs
uicontrol(fg,...
    'style','popupmenu',...
    'units','normalized',...
    'Position',posinpanel([0.12 .05 0.86 .95*1/3],pdirp),...
    lf,...
    'Callback',@click_dir_list,...
    'tag','previous',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'String',pd,...
    'Value',vl);
uicontrol(fg,...
    'style','text',...
    'units','normalized',...
    'Position',posinpanel([0.02 0 0.10 .95*1/3],pdirp),...
          'HorizontalAlignment','left',...
    lf,...
    'BackgroundColor',get(fg,'Color'),...
    'ForegroundColor',col3,...
    'String','Prev');

% Parent dirs
uicontrol(fg,...
    'style','popupmenu',...
    'units','normalized',...
    'Position',posinpanel([0.12 1/3+.05 0.86 .95*1/3],pdirp),...
    lf,...
    'Callback',@click_dir_list,...
    'tag','pardirs',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'String',pardirs(wd));
uicontrol(fg,...
    'style','text',...
    'units','normalized',...
    'Position',posinpanel([0.02 1/3 0.10 .95*1/3],pdirp),...
          'HorizontalAlignment','left',...
    lf,...
    'BackgroundColor',get(fg,'Color'),...
    'ForegroundColor',col3,...
    'String','Up');

% Directory
uicontrol(fg,...
    'style','edit',...
    'units','normalized',...
    'Position',posinpanel([0.12 2/3 0.86 .95*1/3],pdirp),...
    lf,...
    'Callback',@edit_dir,...
    'tag','edit',...
    'BackgroundColor',col1,...
    'ForegroundColor',col3,...
    'String','');
uicontrol(fg,...
    'style','text',...
    'units','normalized',...
    'Position',posinpanel([0.02 2/3 0.10 .95*1/3],pdirp),...
          'HorizontalAlignment','left',...
    lf,...
    'BackgroundColor',get(fg,'Color'),...
    'ForegroundColor',col3,...
    'String','Dir');

resize_fun(fg);
set(fg, 'ResizeFcn',@resize_fun);
update(sel,wd)
set(fg,'windowstyle', 'modal');

waitfor(dne);
drawnow;
if ishandle(sel),
    t  = get(sel,'String');
    if isempty(t)
        t = {''};
    elseif sfilt.code == -1
        % canonicalise non-empty folder selection
        t = cellfun(@(t1)cpath(t1, pwd), t, 'UniformOutput',false);
    end;
    ok = 1;
end;
if ishandle(fg),  delete(fg); end;
drawnow;
return;
%=======================================================================

%=======================================================================
function apos = posinpanel(rpos,ppos)
% Compute absolute positions based on panel position and relative
% position
apos = [ppos(1:2)+ppos(3:4).*rpos(1:2) ppos(3:4).*rpos(3:4)];
%=======================================================================

%=======================================================================
function [pselp, pcntp, pfdp, pdirp] = panelpositions(fg, sellines)
if nargin == 1
    na = numel(get(findobj(fg,'Tag','selected'),'String'));
    n  = get(findobj(fg,'Tag','files'),'Userdata');
    sellines = min([max([n(2) na]), 4]);
end
lf = cfg_get_defaults('cfg_ui.lfont');
bf = cfg_get_defaults('cfg_ui.bfont');
% Create dummy text to estimate character height
t=uicontrol('style','text','string','Xg','units','normalized','visible','off',lf);
lfh = 1.05*get(t,'extent');
delete(t)
t=uicontrol('style','text','string','Xg','units','normalized','visible','off',bf);
bfh = 1.05*get(t,'extent');
delete(t)
% panel heights
% 3 lines for directory, parent and prev directory list
% variable height for dir/file navigation
% 2 lines for buttons, filter etc
% sellines plus scrollbar for selected files
pselh = sellines*lfh(4) + 1.2*lfh(4);
pselp = [0 0 1 pselh];
pcnth = 2*bfh(4);
pcntp = [0 pselh 1 pcnth];
pdirh = 3*lfh(4);
pdirp = [0 1-pdirh 1 pdirh];
pfdh  = 1-(pselh+pcnth+pdirh);
pfdp  = [0 pselh+pcnth 1 pfdh];

%=======================================================================

%=======================================================================
function null(varargin)
%=======================================================================

%=======================================================================
function omsg = msg(ob,str)
ob = sib(ob,'msg');
omsg = get(ob,'String');
set(ob,'String',str);
if nargin>=3,
    set(ob,'ForegroundColor',[1 0 0],'FontWeight','bold');
else
    set(ob,'ForegroundColor',[0 0 0],'FontWeight','normal');
end;
drawnow;
return;
%=======================================================================

%=======================================================================
function setdrive(ob,varargin)
st = get(ob,'String');
vl = get(ob,'Value');
update(ob,st{vl});
return;
%=======================================================================

%=======================================================================
function resize_fun(fg,varargin)
% do nothing
return;
[pselp pcntp pfdp pdirp] = panelpositions(fg);
set(findobj(fg,'Tag','msg'), 'Position',pselp);
set(findobj(fg,'Tag','pcnt'), 'Position',pcntp);
set(findobj(fg,'Tag','pfd'), 'Position',pfdp);
set(findobj(fg,'Tag','pdir'), 'Position',pdirp);
return;
%=======================================================================

%=======================================================================
function [d,mch] = prevdirs(d)
persistent pd
if ~iscell(pd), pd = {}; end;
if nargin == 0
    d = pd;
else
    if ~iscell(d)
        d = cellstr(d);
    end
    d   = unique(d(:));
    mch = cellfun(@(d1)find(strcmp(d1,pd)), d, 'UniformOutput',false);
    sel = cellfun(@isempty, mch);
    npd = numel(pd);
    pd  = [pd(:);d(sel)];
    mch = [mch{~sel} npd+(1:nnz(sel))];
    d = pd;
end
return;
%=======================================================================

%=======================================================================
function pd = pardirs(wd)
if ispc
    fs = '\\';
else
    fs = filesep;
end
pd1 = textscan(wd,'%s','delimiter',fs,'MultipleDelimsAsOne',1);
if ispc
    pd = cell(size(pd1{1}));
    pd{end} = pd1{1}{1};
    for k = 2:numel(pd1{1})
        pd{end-k+1} = fullfile(pd1{1}{1:k},filesep);
    end
else
    pd = cell(numel(pd1{1})+1,1);
    pd{end} = filesep;
    for k = 1:numel(pd1{1})
        pd{end-k} = fullfile(filesep,pd1{1}{1:k},filesep);
    end
end
%=======================================================================

%=======================================================================
function clearfilt(ob,varargin)
set(sib(ob,'regexp'),'String','.*');
update(ob);
return;
%=======================================================================

%=======================================================================
function click_dir_list(ob,varargin)
vl = get(ob,'Value');
ls = get(ob,'String');
update(ob,deblank(ls{vl}));
return;
%=======================================================================

%=======================================================================
function edit_dir(ob,varargin)
update(ob,get(ob,'String'));
return;
%=======================================================================

%=======================================================================
function c = get_current_char(lb)
fg = sib(lb, mfilename);
c = get(fg, 'CurrentCharacter');
if ~isempty(c)
    % reset CurrentCharacter
    set(fg, 'CurrentCharacter', char(13));
end
%=======================================================================

%=======================================================================
function click_dir_box(lb,varargin)
c = get_current_char(lb);
if isempty(c) || isequal(c,char(13))
    vl  = get(lb,'Value');
    str = get(lb,'String');
    pd  = get(sib(lb,'edit'),'String');
    while ~isempty(pd) && strcmp(pd(end),filesep)
        pd=pd(1:end-1);      % Remove any trailing fileseps
    end
    sel = str{vl};
    if strcmp(sel,'..'),     % Parent directory
        [dr odr] = fileparts(pd);
    elseif strcmp(sel,'.'),  % Current directory
        dr = pd;
        odr = '';
    else
        dr = fullfile(pd,sel);
        odr = '';
    end;
    update(lb,dr);
    if ~isempty(odr)
        % If moving up one level, try to set focus on previously visited
        % directory
        cdrs = get(lb, 'String');
        dind = find(strcmp(odr, cdrs));
        if ~isempty(dind)
            set(lb, 'Value',dind(1));
        end
    end
end
return;
%=======================================================================

%=======================================================================
function re = getfilt(ob)
ob  = sib(ob,'regexp');
ud  = get(ob,'UserData');
re  = struct('code',ud.code,...
             'frames',get(sib(ob,'frame'),'UserData'),...
             'ext',{ud.ext},...
             'filt',{{get(sib(ob,'regexp'),'String')}});
return;
%=======================================================================

%=======================================================================
function update(lb,dr)
lb = sib(lb,'dirs');
if nargin<2 || isempty(dr) || ~ischar(dr)
    dr = get(lb,'UserData');
end;
if ~(strcmpi(computer,'PCWIN') || strcmpi(computer,'PCWIN64'))
    dr    = [filesep dr filesep];
else
    dr    = [dr filesep];
end;
dr(strfind(dr,[filesep filesep])) = [];
[f,d] = listfiles(dr,getfilt(lb));
if isempty(d),
    dr    = get(lb,'UserData');
    [f,d] = listfiles(dr,getfilt(lb));
else
    set(lb,'UserData',dr);
end;
set(lb,'Value',1,'String',d);
set(sib(lb,'files'),'Value',1,'String',f);
set(sib(lb,'pardirs'),'String',pardirs(dr),'Value',1);
[ls,mch] = prevdirs(dr);
set(sib(lb,'previous'),'String',ls,'Value',mch);
set(sib(lb,'edit'),'String',dr);

if numel(dr)>1 && dr(2)==':',
    str = char(get(sib(lb,'drive'),'String'));
    mch = find(lower(str(:,1))==lower(dr(1)));
    if ~isempty(mch),
        set(sib(lb,'drive'),'Value',mch);
    end;
end;
return;
%=======================================================================

%=======================================================================
function update_frames(lb,varargin)
str = get(lb,'String');
%r   = get(lb,'UserData');
try
    r = eval(['[',str,']']);
catch
    msg(lb,['Failed to evaluate "' str '".'],'r');
    beep;
    return;
end;
if ~isnumeric(r),
    msg(lb,['Expression non-numeric "' str '".'],'r');
    beep;
else
    set(lb,'UserData',r);
    msg(lb,'');
    update(lb);
end;
%=======================================================================

%=======================================================================
function select_all(ob,varargin)
lb = sib(ob,'files');
set(lb,'Value',1:numel(get(lb,'String')));
drawnow;
click_file_box(lb);
return;
%=======================================================================

%=======================================================================
function click_file_box(lb,varargin)
c = get_current_char(lb);
if isempty(c) || isequal(c, char(13))
    vlo  = get(lb,'Value');
    if isempty(vlo),
        msg(lb,'Nothing selected');
        return;
    end;
    lim  = get(lb,'UserData');
    ob   = sib(lb,'selected');
    str3 = get(ob,'String');
    str  = get(lb,'String');
    lim1  = min([max([lim(2)-numel(str3),0]),numel(vlo)]);
    if lim1==0,
        msg(lb,['Selected ' num2str(size(str3,1)) '/' num2str(lim(2)) ' already.']);
        beep;
        set(sib(lb,'D'),'Enable','on');
        return;
    end;

    vl   = vlo(1:lim1);
    msk  = false(size(str,1),1);
    if vl>0, msk(vl) = true; else msk = []; end;
    str1 = str( msk);
    str2 = str(~msk);
    dr   = get(sib(lb,'edit'), 'String');
    str1 = strcat(dr, str1);

    set(lb,'Value',min([vl(1),numel(str2)]),'String',str2);
    r    = (1:numel(str1))+numel(str3);
    str3 = [str3(:);str1(:)];
    set(ob,'String',str3,'Value',r);
    if numel(vlo)>lim1,
        msg(lb,['Retained ' num2str(lim1) '/' num2str(numel(vlo))...
            ' of selection.']);
        beep;
    elseif isfinite(lim(2))
        if lim(1)==lim(2),
            msg(lb,['Selected ' num2str(numel(str3)) '/' num2str(lim(2)) ' files.']);
        else
            msg(lb,['Selected ' num2str(numel(str3)) '/' num2str(lim(1)) '-' num2str(lim(2)) ' files.']);
        end;
    else
        if size(str3,1) == 1, ss = ''; else ss = 's'; end;
        msg(lb,['Selected ' num2str(numel(str3)) ' file' ss '.']);
    end;
    if ~isfinite(lim(1)) || numel(str3)>=lim(1),
        set(sib(lb,'D'),'Enable','on');
    end;
end
return;
%=======================================================================

%=======================================================================
function obj = sib(ob,tag)
persistent fg;
if isempty(fg) || ~ishandle(fg)
    fg = findobj(0,'Tag',mfilename);
end
obj = findobj(fg,'Tag',tag);
return;
%if isempty(obj),
%    cfg_message('matlabbatch:usage',['Can''t find object with tag "' tag '".']);
%elseif length(obj)>1,
%    cfg_message('matlabbatch:usage',['Found ' num2str(length(obj)) ' objects with tag "' tag '".']);
%end;
%return;
%=======================================================================

%=======================================================================
function unselect(lb,varargin)
vl      = get(lb,'Value');
if isempty(vl), return; end;
str     = get(lb,'String');
msk     = true(numel(str),1);
if vl~=0, msk(vl) = false; end;
str2    = str(msk);
set(lb,'Value',min(vl(1),numel(str2)),'String',str2);
lim = get(sib(lb,'files'),'UserData');
if numel(str2)>= lim(1) && numel(str2)<= lim(2),
    set(sib(lb,'D'),'Enable','on');
else
    set(sib(lb,'D'),'Enable','off');
end;

if numel(str2) == 1, ss1 = ''; else ss1 = 's'; end;
%msg(lb,[num2str(size(str2,1)) ' file' ss ' remaining.']);
if numel(vl) == 1, ss = ''; else ss = 's'; end;
msg(lb,['Unselected ' num2str(numel(vl)) ' file' ss '. ' ...
        num2str(numel(str2)) ' file' ss1 ' remaining.']);
return;
%=======================================================================

%=======================================================================
function unselect_all(ob,varargin)
lb = sib(ob,'selected');
set(lb,'Value',[],'String',{},'ListBoxTop',1);
msg(lb,'Unselected all files.');
lim = get(sib(lb,'files'),'UserData');
if lim(1)>0, set(sib(lb,'D'),'Enable','off'); end;
return;
%=======================================================================

%=======================================================================
function [f,d] = listfiles(dr,filt)
try
    ob = sib(gco,'msg');
    domsg = ~isempty(ob);
catch
    domsg = false;
end
if domsg
    omsg = msg(ob,'Listing directory...');
end
if nargin<2, filt = '';  end;
if nargin<1, dr   = '.'; end;
de      = dir(dr);
if ~isempty(de),
    d     = {de([de.isdir]).name};
    if ~any(strcmp(d, '.'))
        d = [{'.'}, d(:)'];
    end;
    if filt.code~=-1,
        f = {de(~[de.isdir]).name};
    else
        % f = d(3:end);
        f = d;
    end;
else
    d = {'.','..'};
    f = {};
end;

if domsg
    msg(ob,['Filtering ' num2str(numel(f)) ' files...']);
end
f  = do_filter(f,filt.ext);
f  = do_filter(f,filt.filt);
ii = cell(1,numel(f));
if filt.code==1 && (numel(filt.frames)~=1 || filt.frames(1)~=1),
    if domsg
        msg(ob,['Reading headers of ' num2str(numel(f)) ' images...']);
    end
    for i=1:numel(f),
        try
            ni = nifti(fullfile(dr,f{i}));
            dm = [ni.dat.dim 1 1 1 1 1];
            d4 = (1:dm(4))';
        catch
            d4 = 1;
        end;
        if all(isfinite(filt.frames))
            msk = false(size(filt.frames));
            for j=1:numel(msk), msk(j) = any(d4==filt.frames(j)); end;
            ii{i} = filt.frames(msk);
        else
            ii{i} = d4;
        end;
    end
elseif filt.code==1 && (numel(filt.frames)==1 && filt.frames(1)==1),
    for i=1:numel(f),
        ii{i} = 1;
    end;
end;

if domsg
    msg(ob,['Listing ' num2str(numel(f)) ' files...']);
end

[f,ind] = sortrows(f(:));
ii      = ii(ind);
msk     = true(1,numel(f));
for i=2:numel(f),
    if strcmp(f{i-1},f{i}),
        if filt.code==1,
            tmp      = sort([ii{i}(:) ; ii{i-1}(:)]);
            tmp(~diff(tmp,1)) = [];
            ii{i}    = tmp;
        end;
        msk(i-1) = false;
    end;
end;
f        = f(msk);
if filt.code==1,
    % Combine filename and frame number(s)
    ii       = ii(msk);
    nii      = cellfun(@numel, ii);
    c        = cell(sum(nii),1);
    fi       = cell(numel(f),1);
    for k = 1:numel(fi)
        fi{k} = k*ones(1,nii(k));
    end
    ii = [ii{:}];
    fi = [fi{:}];
    for i=1:numel(c),
        c{i} = sprintf('%s,%d', f{fi(i)}, ii(i));
    end;
    f        = c;
elseif filt.code==-1,
    fs = filesep;
    for i=1:numel(f),
        f{i} = [f{i} fs];
    end;
end;
f        = f(:);
d        = unique(d(:));
if domsg
    msg(ob,omsg);
end
return;
%=======================================================================

%=======================================================================
function [f,ind] = do_filter(f,filt)
t2 = false(numel(f),1);
filt_or = sprintf('(%s)|',filt{:});
t1 = regexp(f,filt_or(1:end-1));
if numel(f)==1 && ~iscell(t1), t1 = {t1}; end;
for i=1:numel(t1),
    t2(i) = ~isempty(t1{i});
end;
ind = find(t2);
f   = f(t2);
return;
%=======================================================================

%=======================================================================
function heelp(ob,varargin)
[col1,col2,col3,fn] = colours;
fg = sib(ob,mfilename);
t  = uicontrol(fg,...
    'style','listbox',...
    'units','normalized',...
    'Position',[0.01 0.01 0.98 0.98],...
    fn,...
    'BackgroundColor',col2,...
    'ForegroundColor',col3,...
    'Max',0,...
    'Min',0,...
    'tag','HelpWin',...
    'String','                   ');
c0 = uicontextmenu('Parent',fg);
set(t,'uicontextmenu',c0);
uimenu('Label','Done', 'Parent',c0,'Callback',@helpclear);

str  = cfg_justify(t, {[...
'File Selection help. You can return to selecting files via the right mouse button (the "Done" option). '],...
'',[...
'The panel at the bottom shows files that are already selected. ',...
'Clicking a selected file will un-select it. To un-select several, you can ',...
'drag the cursor over the files, and they will be gone on release. ',...
'You can use the right mouse button to un-select everything.'],...
'',[...
'Directories are navigated by editing the name of the current directory (where it says "Dir"), ',...
'by going to one of the previously entered directories ("Prev"), ',...
'by going to one of the parent directories ("Up") or by navigating around ',...
'the parent or subdirectories listed in the left side panel.'],...
'',[...
'Files matching the filter ("Filt") are shown in the panel on the right. ',...
'These can be selected by clicking or dragging.  Use the right mouse button if ',...
'you would like to select all files.  Note that when selected, the files disappear ',...
'from this panel.  They can be made to reappear by re-specifying the directory ',...
'or the filter. '],...
'',[...
'Both directory and file lists can also be browsed by typing the leading ',...
'character(s) of a directory or file name.'],...
'',[...
'Note that the syntax of the filter differs from that used by most other file selectors. ',...
'The filter works using so called ''regular expressions''. Details can be found in the ',...
'MATLAB help text on ''regexp''. ',...
'The following is a list of symbols with special meaning for filtering the filenames:'],...
'    ^     start of string',...
'    $     end of string',...
'    .     any character',...
'    \     quote next character',...
'    *     match zero or more',...
'    +     match one or more',...
'    ?     match zero or one, or match minimally',...
'    {}    match a range of occurrances',...
'    []    set of characters',...
'    [^]   exclude a set of characters',...
'    ()    group subexpression',...
'    \w    match word [a-z_A-Z0-9]',...
'    \W    not a word [^a-z_A-Z0-9]',...
'    \d    match digit [0-9]',...
'    \D    not a digit [^0-9]',...
'    \s    match white space [ \t\r\n\f]',...
'    \S    not a white space [^ \t\r\n\f]',...
'    \<WORD\>    exact word match',...
'',[...
'Individual time frames of image files can also be selected.  The frame filter ',...
'allows specified frames to be shown, which is useful for image files that ',...
'contain multiple time points.  If your images are only single time point, then ',...
'reading all the image headers can be avoided by specifying a frame filter of "1". ',...
'The filter should contain a list of integers indicating the frames to be used. ',...
'This can be generated by e.g. "1:100", or "1:2:100".'],...
'',[...
'The recursive selection button (Rec) allows files matching the regular expression to ',...
'be recursively selected.  If there are many directories to search, then this can take ',...
'a while to run.'],...
'',[...
'There is also an edit button (Ed), which allows you to edit your selection of files. ',...
'When you are done, then use the menu-button of your mouse to either cancel or accept your changes.'],''});
set(t,'String',str);
return;
%=======================================================================

%=======================================================================
function helpclear(ob,varargin)
ob = get(ob,'Parent');
ob = get(ob,'Parent');
ob = findobj(ob,'Tag','HelpWin');
delete(ob);
%=======================================================================

%=======================================================================
function t = cpath(t,d)
switch filesep,
case '/',
    mch = '^/';
    fs  = '/';
    fs1 = '/';
case '\',
    mch = '^.:\\';
    fs  = '\';
    fs1 = '\\';
otherwise;
    cfg_message('matlabbatch:usage','What is this filesystem?');
end

if isempty(regexp(t,mch,'once')),
    if (nargin<2)||isempty(d), d = pwd; end;
    t = [d fs t];
end;

% Replace occurences of '/./' by '/' (problems with e.g. /././././././')
re = [fs1 '\.' fs1];
while ~isempty(regexp(t,re, 'once' )),
    t  = regexprep(t,re,fs);
end;
t  = regexprep(t,[fs1 '\.' '$'], fs);

% Replace occurences of '/abc/../' by '/'
re = [fs1 '[^' fs1 ']+' fs1 '\.\.' fs1];
while ~isempty(regexp(t,re, 'once' )),
    t  = regexprep(t,re,fs,'once');
end;
t  = regexprep(t,[fs1 '[^' fs1 ']+' fs1 '\.\.' '$'],fs,'once');

% Replace '//'
t  = regexprep(t,[fs1 '+'], fs);
%=======================================================================

%=======================================================================
function editwin(ob,varargin)
[col1,col2,col3,lf,bf] = colours;
fg   = gcbf;
lb   = sib(ob,'selected');
str  = get(lb,'String');
ac   = allchild(fg);
acv  = get(ac,'Visible');
h    = uicontrol(fg,...
    'Style','Edit',...
    'units','normalized',...
    'String',str,...
    lf,...
    'Max',2,...
    'Tag','EditWindow',...
    'HorizontalAlignment','Left',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'Position',[0.01 0.08 0.98 0.9],...
    'Userdata',struct('ac',{ac},'acv',{acv}));
ea   = uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',[.01 .01,.32,.07],...
    bf,...
    'Callback',@editdone,...
    'tag','EditWindowAccept',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','Accept');
ee   = uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',[.34 .01,.32,.07],...
    bf,...
    'Callback',@editeval,...
    'tag','EditWindowEval',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','Eval');
ec   = uicontrol(fg,...
    'Style','pushbutton',...
    'units','normalized',...
    'Position',[.67 .01,.32,.07],...
    bf,...
    'Callback',@editclear,...
    'tag','EditWindowCancel',...
    'ForegroundColor',col3,...
    'BackgroundColor',col1,...
    'String','Cancel');
set(ac,'visible','off');
%=======================================================================

%=======================================================================
function editeval(ob,varargin)
ob  = get(ob, 'Parent');
ob  = sib(ob, 'EditWindow');
str = get(ob, 'String');
if ~isempty(str)
    [out,sts] = cfg_eval_valedit(char(str));
    if sts && (iscellstr(out) || ischar(out))
        set(ob, 'String', cellstr(out));
    else
        fgc = get(ob, 'ForegroundColor');
        set(ob, 'ForegroundColor', 'red');
        pause(1);
        set(ob, 'ForegroundColor', fgc);
    end
end
%=======================================================================

%=======================================================================
function editdone(ob,varargin)
ob  = get(ob,'Parent');
ob  = sib(ob,'EditWindow');
str = cellstr(get(ob,'String'));
if isempty(str) || isempty(str{1})
    str = {};
else
    dstr = deblank(str);
    if ~isequal(str, dstr)
        c = questdlg(['Some of the filenames contain trailing blanks. This may ' ...
            'be due to copy/paste of strings between MATLAB and the ' ...
            'edit window. Do you want to remove any trailing blanks?'], ...
            'Trailing Blanks in Filenames', ...
            'Remove', 'Keep', 'Remove');
        switch lower(c)
            case 'remove'
                str = dstr;
        end
    end
    filt = getfilt(ob);
    if filt.code >= 0 % filter files, but not dirs
        [p,n,e] = cellfun(@fileparts, str, 'uniformoutput',false);
        fstr = strcat(n, e);
        [fstr1,fsel] = do_filter(fstr, filt.ext);
        str = str(fsel);
    end
end
lim = get(sib(ob,'files'),'UserData');
if numel(str)>lim(2),
    msg(ob,['Retained ' num2str(lim(2)) ' of the ' num2str(numel(str)) ' files.']);
    beep;
    str = str(1:lim(2));
elseif isfinite(lim(2)),
    if lim(1)==lim(2),
        msg(ob,['Selected ' num2str(numel(str)) '/' num2str(lim(2)) ' files.']);
    else
        msg(ob,['Selected ' num2str(numel(str)) '/' num2str(lim(1)) '-' num2str(lim(2)) ' files.']);
    end;
else
    if numel(str) == 1, ss = ''; else ss = 's'; end;
    msg(ob,['Specified ' num2str(numel(str)) ' file' ss '.']);
end;
if ~isfinite(lim(1)) || numel(str)>=lim(1),
    set(sib(ob,'D'),'Enable','on');
else
    set(sib(ob,'D'),'Enable','off');
end;
set(sib(ob,'selected'),'String',str,'Value',[]);
acs = get(ob,'Userdata');
fg = gcbf;
delete(findobj(fg,'-regexp','Tag','^EditWindow.*'));
set(acs.ac,{'Visible'},acs.acv);
%=======================================================================

%=======================================================================
function editclear(ob,varargin)
fg = gcbf;
acs = get(findobj(fg,'Tag','EditWindow'),'Userdata');
delete(findobj(fg,'-regexp','Tag','^EditWindow.*'));
set(acs.ac,{'Visible'},acs.acv);
%=======================================================================

%=======================================================================
function [c1,c2,c3,lf,bf] = colours
c1 = [1 1 1];
c2 = [1 1 1];
c3 = [0 0 0];
lf = cfg_get_defaults('cfg_ui.lfont');
bf = cfg_get_defaults('cfg_ui.bfont');
if isempty(lf)
    lf = struct('FontName',get(0,'FixedWidthFontName'), ...
                'FontWeight','normal', ...
                'FontAngle','normal', ...
                'FontSize',14, ...
                'FontUnits','points');
end
if isempty(bf)
    bf = struct('FontName',get(0,'FixedWidthFontName'), ...
                'FontWeight','normal', ...
                'FontAngle','normal', ...
                'FontSize',14, ...
                'FontUnits','points');
end
%=======================================================================

%=======================================================================
function select_rec(ob, varargin)
start = get(sib(ob,'edit'),'String');
filt  = get(sib(ob,'regexp'),'Userdata');
filt.filt = {get(sib(ob,'regexp'), 'String')};
fob = sib(ob,'frame');
if ~isempty(fob)
    filt.frames = get(fob,'Userdata');
else
    filt.frames = [];
end;
ptr    = get(gcbf,'Pointer');
try
    set(gcbf,'Pointer','watch');
    sel    = select_rec1(start,filt);
catch
    set(gcbf,'Pointer',ptr);
    sel = {};
end;
set(gcbf,'Pointer',ptr);
already= get(sib(ob,'selected'),'String');
fb     = sib(ob,'files');
lim    = get(fb,'Userdata');
limsel = min(lim(2)-size(already,1),size(sel,1));
set(sib(ob,'selected'),'String',[already(:);sel(1:limsel)],'Value',[]);
msg(ob,sprintf('Added %d/%d matching files to selection.', limsel, size(sel,1)));
if ~isfinite(lim(1)) || size(sel,1)>=lim(1),
    set(sib(ob,'D'),'Enable','on');
else
    set(sib(ob,'D'),'Enable','off');
end;
%=======================================================================

%=======================================================================
function sel=select_rec1(cdir,filt)
sel={};
[t,d] = listfiles(cdir,filt);
if ~isempty(t)
    sel = strcat([cdir,filesep],t);
end;
for k = 1:numel(d)
    if ~any(strcmp(d{k},{'.','..'}))
        sel1 = select_rec1(fullfile(cdir,d{k}),filt);
        sel  = [sel(:); sel1(:)];
    end;
end;
%=======================================================================

%=======================================================================
function sfilt=mk_filter(typ,filt,frames)
if nargin<3, frames  = 1;     end;
if nargin<2, filt    = '.*';    end;
if nargin<1, typ     = 'any';   end;
switch lower(typ),
case {'any','*'}, code = 0; ext = {'.*'};
case {'image'},   code = 1; ext = {'.*\.nii(,\d+){0,2}$','.*\.img(,\d+){0,2}$','.*\.NII(,\d+){0,2}$','.*\.IMG(,\d+){0,2}$'};
case {'mesh'},    code = 0; ext = {'.*\.gii$','.*\.GII$','.*\.mat$','.*\.MAT$'};
case {'nifti'},   code = 0; ext = {'.*\.nii$','.*\.img$','.*\.NII$','.*\.IMG$'};
case {'gifti'},   code = 0; ext = {'.*\.gii$','.*\.GII$'};
case {'extimage'},   code = 1; ext = {'.*\.nii(,[0-9]*){0,2}$',...
                            '.*\.img(,[0-9]*){0,2}$',...
                            '.*\.NII(,[0-9]*){0,2}$',...
                            '.*\.IMG(,[0-9]*){0,2}$'};
case {'xml'},     code = 0; ext = {'.*\.xml$','.*\.XML$'};
case {'mat'},     code = 0; ext = {'.*\.mat$','.*\.MAT$','.*\.txt','.*\.TXT'};
case {'batch'},   code = 0; ext = {'.*\.mat$','.*\.MAT$','.*\.m$','.*\.M$','.*\.xml$','.*\.XML$'};
case {'dir'},     code =-1; ext = {'.*'};
case {'extdir'},     code =-1; ext = {['.*' filesep '$']};
otherwise,        code = 0; ext = {typ};
end;
sfilt = struct('code',code,'frames',frames,'ext',{ext},...
                             'filt',{{filt}});
%=======================================================================

%=======================================================================
function drivestr = listdrives(reread)
persistent mydrivestr;
if isempty(mydrivestr) || reread
    driveLett = strcat(cellstr(char(('C':'Z')')), ':');
    dsel = false(size(driveLett));
    for i=1:numel(driveLett)
        dsel(i) = exist([driveLett{i} '\'],'dir')~=0;
    end
    mydrivestr = driveLett(dsel);
end;
drivestr = mydrivestr;
%=======================================================================

%=======================================================================


function showTheseRois = dtiROIShowList(handles)
%
%   showTheseRois = dtiROIShowList(handles);
%
%Author: Dougherty, Wandell
%Purpose
%   Based on the figure settings, including the number of ROIs and the
%   ShowMode build a vector of the ROIs that should be displayed.  Used in
%   various drawing and plotting routines.
%

if (handles.roiShowMode == 1) ...
        || isempty(handles.rois) ...
        || ((length(handles.rois) == 1) && strcmp(handles.rois.name,'Empty'))
    showTheseRois = [];
elseif (handles.roiShowMode == 2),  showTheseRois = handles.curRoi;
elseif (handles.roiShowMode == 3),  showTheseRois = (1:length(handles.rois));
elseif (handles.roiShowMode == 4),  showTheseRois = (1:length(handles.rois));
else error('Bad handles.roiShowMode');
end

return;


function [TorF, vstr, rdate] = have_feature_syngrid()
%HAVE_FEATURE_SYNGRID  Detect availability/version info for SynGrid
%
%   Feature detection function implementing 'syngrid' tag for HAVE_FEATURE
%   to detect availability/version of SynGrid, Synthetic Grid Creation for
%   MATPOWER (https://github.com/MATPOWER/mx-syngrid).
%
%   See also HAVE_FEATURE, SYNGRID.

%   MATPOWER
%   Copyright (c) 2004-2020, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

TorF = exist('syngrid', 'file') == 2;
if TorF
    v = sgver('all');
    vstr = v.Version;
    rdate = v.Date;
else
    vstr = '';
    rdate = '';
end


% READTXTFILE - Read text file into a Matlab variable
%
% Usage: >> str = readtxtfile( filename );
%
% Input:
%   filename - [string] name of the file.
%
% Output:
%   str - [string] content of the file.
%
% Author: Arnaud Delorme, SCCN / INC / UCSD, August 2009

% Copyright (C) Arnaud Delorme, August 2009
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function com = readtxtfile(filename)

    com = '';
    if ~exist(filename)
        disp([ 'Cannot find option file ' filename ]);
    else
        fid = fopen(filename, 'r');
        if fid == -1
            disp([ 'Cannot open option file ' filename ]);
        else
            com = '';
            while ~feof(fid)
                a = fgetl(fid);
                com = [ com 10 a ];
            end
            fclose(fid);
        end
    end


function wex(directory)
% WEX opens the Windows Explorer in selected Directory
%
%   WEX does call the Windows Explorer in the wished Directory, which the
%   user does enter as input.
%   One Option is only to enter a single letter from the Root Drive or to
%   enter a complete Path Directory information.
%
%   Options: >> wex                   - Opens in the Current Directory
%            >> wex d                 - Opens Explorer in path 'd:\'
%            >> wex d:\applications   - Opens Path in 'd:\applications'
%            >> wex; wex('d'); wex('d:\applications') - Opens all
%

%% AUTHOR    : Frank Gonzalez-Morphy
%% $DATE     : 13-Mar-2001 18:10:27 $
%% $Revision : 1.12 $
%% DEVELOPED : R12.0
%% FILENAME  : wex.m

if ~ispc
    error('  :: Sorry these is only available under Windows !')
end

if nargin == 0
    choice = 'curdir';   % Current Directory
elseif nargin >= 1
    choice = 'adr';
end

switch choice
    case 'curdir'
        % Opens the Windows Explorer in current Directory (= pwd)
        !explorer /e, .\ &
    case 'adr'
        % If input is only one character, like C D M then the path
        % will be completed like c:\, d:\ or M:\.
        % If Input has more then just one letter, then the addrss
        % will be keep and Windows Explorer opened there.
        if length(directory) == 1
            ewrd = strcat('explorer /e,', directory);     % explorer /e,d
            exw = ':\ &';                                   % :\
            comd = [ewrd exw];                            % explorer /e,d:\
            dos(comd);
        else
            comd = strcat('explorer /e,', directory);     % explorer /e, D:\applications
            dos(comd);
        end
    otherwise
        disp('  :: Wrong Input, Type ... help wex ... for correct Options !')
end

% Created with NEWFCN.m by Frank González-Morphy
% Contact...: frank.gonzalez-morphy@mathworks.de
% ===== EOF ====== [wex.m] ======


function [ eqtc, varargout ] = import_events(dbname, chanfile, starttime, endtime, phase, delay, enddelay, varargin)
%IMPORT_EVENTS Import waveforms associated with earthquake events.
% EQTC = IMPORT_EVENTS(DBNAME, CHANFILE, STARTTIME, ENDTIME, ...
%                      PHASE, DELAY, ENDDELAY) returns a cell array of
% threecomp objects, waveform objects, and empty cells, containing
% waveforms organized column-wise by station and row-wise by event.  Input
% arguments are as follows:
%
%    DBNAME: Filename of an Antelope database.
%    CHANFILE: Filename of a comma-delimited list of station-channel
%    associations (see IE_READCHANS helpfile for the format).
%    STARTTIME: Start date/time in Antelope-readable string format.
%    ENDTIME: End date/time in Antelope-readable string format.
%    PHASE: Arrival phase to use (e.g. 'P', 'S')
%    DELAY: Seconds preceding arrival time to retrieve into waveforms.
%    ENDDELAY: Seconds following arrival time to retrieve into waveforms.
%
% The intent of this function is to import waveform data from multiple
% events and stations, and to store it in an organized cell array which
% preserves associations between arrivals.  Three-component data is
% retrieved if it is available, and converted to threecomp objects.
% Single-component data remains as waveform objects.  Values ETYPE, ORID,
% OTIME, SEAZ, and STYPE are also retrieved from the database and stored as
% custom fields within each waveform object.  If there is no arrival for an
% event at a station, that cell is left empty.  If three-component data is
% available but cannot be converted into a threecomp object (usually
% indicating data problems), the cell is also left empty.
%
% [EQTC, EQWF] = IMPORT_EVENTS(...) returns the aforementioned cell array
% in EQTC as well as a cell array of unadulterated waveform objects in
% EQWF.  These will be 1x1 waveforms for single-component data and 1x3
% waveforms for three-component data (including those that could not be
% converted to threecomp objects and are not present in EQTC).
%
% ... = IMPORT_EVENTS(..., DS) uses specified GISMO datasource DS to
% retrieve the waveform data.  The earthquake catalogue and phase arrivals
% are always taken from the Antelope database DBNAME, but actual waveform
% data can be in any GISMO-readable format.  Examples: DS =
% datasource('uaf_continuous') or DS =
% datasource('winston','myserver.uaf.edu',12345).
%
% RETURN VALUE EXAMPLES
%
% In both EQTC and EQWF, waveforms are organized into columns by station
% and rows by event.  Here is a hypothetical three-station five-earthquake
% example; stations EG1 and EG2 are three-component, station EG3 is
% single-component.
%
% EQTC =  % EG1                EG2                EG3
%                  []    [1x1 threecomp]    [1x1 waveform]   % Event 1
%     [1x1 threecomp]    [1x1 threecomp]                []   % Event 2
%                  []                 []    [1x1 waveform]   % Event 3
%     [1x1 threecomp]    [1x1 threecomp]    [1x1 waveform]   % Event 4
%                  []                 []    [1x1 waveform]   % Event 5
%
% EQWF =  % EG1                EG2                EG3
%                  []     [1x3 waveform]    [1x1 waveform]   % Event 1
%      [1x3 waveform]     [1x3 waveform]                []   % Event 2
%                  []                 []    [1x1 waveform]   % Event 3
%      [1x3 waveform]     [1x3 waveform]    [1x1 waveform]   % Event 4
%      [1x3 waveform]                 []    [1x1 waveform]   % Event 5
%
% Notice that EQTC contains both threecomp objects and waveform objects.
% EQWF contains waveform objects only, but with size 1x3 for
% three-component stations and 1x1 for single-component stations.  When
% events are not recorded at a station, the cell is left empty [].
% IMPORTANT: The cell in EQTC is also left empty when a 1x3 waveform cannot
% be converted into a threecomp object, such as with event 5 at station EG1 in
% the above example.
%
% CAVEATS AND LIMITATIONS:
%
% 1. The earthquake list is taken from the origin table without
% consideration of the event table or the value of prefor.  If more than
% one origin is present for an event, they will all be retrieved.
%
% 2. The value of STYPE is taken from the chosen phase arrival, and is assumed
% to be the same across all components of three-component data.
%
% 3. Unassociated arrivals will not be retrieved.  The returned data
% structure is versatile enough that unassociated arrivals _could_ be
% stored in an intuitive way (one per row) but so far this has not been
% implemented.
%
% Author: Christopher Bruton, Geophysical Institute, University of Alaska Fairbanks
% $Date$
% $Revision$

warning('This function likely broken. It needs to be rebuilt on top of core GISMO classes for Catalog and Arrival objects')
%% Check for valid input

% dbname is checked by dbopen()

% chanfile is checked by ie_readchans()

% Convert dates.  We could pass the user input directly to Antelope in the
% query, but malformed dates could cause unexpected results.  There doesn't
% seem to be a simple way to otherwise check for valid date formats or to
% escape special characters. This way we at least ensure a valid epoch number.
starttime = str2epoch(starttime);
endtime = str2epoch(endtime);

% Set flag if dates are not given; this will prevent subset on starttime
% and endtime and instead return the whole catalogue.  Note that
% str2epoch()'s return value does not distinguish between invalid input
% (e.g. 'asdfjd') and true zero values (e.g. 'Jan 1 1970'), so this flag
% could get set inadvertently.
if (starttime == 0) && (endtime == 0)
	all_times = true;
else
	all_times = false;
	% Check date range
	if (endtime < starttime)
		error('Argument endtime must be after argument starttime.');
	end
end

% Check delay times.  These should be numbers
if ~isscalar(delay) || ~isnumeric(delay)
	error('Argument delay must be a scalar number.');
end
if ~isscalar(enddelay) || ~isnumeric(enddelay)
	error('Argument enddelay must be a scalar number.');
end

% Check that waveform time range is valid
if (enddelay + delay) <= 0
	error('Arguments delay and enddelay must denote a positive non-zero time range.');
end

% Check that phase is an alphanumeric string (perhaps this cannot be
% expected to be universally true, but the alternative would require
% checking that special characters are safe).
if ~all(isstrprop(phase,'alphanum'))
	error('Argument phase must be an alphanumeric string.');
end

% Check if optional datasource object was provided
switch size(varargin,2)
	case 0
		ds = datasource('antelope',dbname); % Standard antelope datasource
	case 1
		ds = varargin{1}; % User-supplied datasource
	otherwise
		error('Incorrect number of input arguments.');
end

%% Check output arguments

switch nargout
	case 1
		ret_eqwf = false;
	case 2
		ret_eqwf = true;
	otherwise
		error('Incorrect number of output arguments.');
end

%% Read and verify list of stations and channels

fprintf(2,'Loading and verifying station/channel list...\n');

% Open list
chanlist = ie_readchans(chanfile);

% The above function does some basic error checking but we still need to
% confirm things with the database (sitechan table)

% Open DB, read sitechan table, and close
db = dbopen(dbname,'r');
db = dblookup_table(db,'sitechan');
db = dbsort(db,'sta','chan');
[tsta,tchan] = dbgetv(db,'sta','chan');
dbclose(db);

% Now check that every station-channel pair in chanlist exists
stations = chanlist(:,1);
for n = 1:length(stations)
	channels = chanlist(n,2:4);
	channels(strcmp('',channels)) = []; % Delete empty elements

	tchans = tchan(strcmp(stations(n),tsta)); % Channels from tchan corresponding to station n
	for m = 1:length(channels)
		if ~any(strcmp(channels(m),tchans))
			error(['Channel ' channels{m} ' at station ' stations{n} ' does not exist in sitechan table.']);
		end
	end
end

%% Load the earthquake catalogue

fprintf(2,'Loading earthquake catalogue...');

% Open database
db = dbopen(dbname,'r');

% Get all arrivals (of desired phase) for origins within time period
db = dblookup_table(db,'origin');
if all_times == false
	db = dbsubset(db,['time >= ' num2str(starttime,'%f') ' && time < ' num2str(endtime,'%f')]);
end
db1 = dblookup_table(db,'assoc');
db1 = dbsubset(db1,['phase == "' phase '"']);
db = dbjoin(db,db1);

% Create station subset string
stastring = ['sta=="' stations{1} '"'];
if length(stations) > 1
	for n = 1:length(stations)
		stastring = [stastring ' || sta=="' stations{n} '"'];
	end
end

% Subset to only chosen stations
db = dbsubset(db,stastring);

% Join to arrival table
db1 = dblookup_table(db,'arrival');
db = dbjoin(db,db1);

% Sort by origin time and id
db = dbsort(db,'origin.time','orid');

% Get the values
[sta,atime,otime,orid,etype,stype,seaz] = dbgetv(db,'sta','arrival.time','origin.time','orid','etype','stype','seaz');
atime = epoch2datenum(atime);
otime = epoch2datenum(otime);

% We also want a list of unique orids.  Have to use DB operations for this
% (rather than unique()) so that sorting (by time) is preserved
db = dbgroup(db,{'origin.time','orid'});
unique_orids = dbgetv(db,'orid');

% Close database link
dbclose(db);

% Display counts
fprintf(2,'%d origins, %d arrivals\n',numel(unique_orids),numel(atime));

%% Arrange info from earthquake catalogue into a more useful format

% We want a column for each station and a row for each earthquake (origin).
% Store arrival times in this format for now, so we can later replace them
% with loaded waveforms.

% Number of stations.  Use user-specified list and order (rather than
% unique values from arrivals table) for greater flexibility.
nstations = length(stations);

% Number of earthquakes
norigins = length(unique_orids);

% Allocate an array to store arrival times
eqtimes = zeros(norigins,nstations);

% Allocate an additional array to store the indices, so we can retrieve
% other data (e.g. event type) later
eqindices = zeros(norigins,nstations);

% Now go through each arrival and place it in the array
for n = 1:length(orid)
	sta_index = find(strcmp(sta{n},stations),1);
	orid_index = find(unique_orids == orid(n),1); % Could do something more efficient here; orid is sorted in ascending order so a find is not really necessary
	eqtimes(orid_index,sta_index) = atime(n);
	eqindices(orid_index,sta_index) = n;
end

%% Load waveforms for all arrivals

% We now have an array full of arrival times at stations.  We want to load
% those waveforms into a cell array of the same shape.

% Allocate the array
eqwf = cell(norigins,nstations);

fprintf(2,'Loading waveforms:\n');
% We'll go column by column
for ns = 1:nstations
	% Get channel names and create scnlobject
	channels = chanlist(ns,2:4);
	channels(strcmp('',channels)) = [];
	scnl = scnlobject(stations{ns},channels,'','');

	% Is this one or three components?
	if length(channels) == 3
		is_tc = true;
	else
		is_tc = false;
	end
	fprintf(2,'%s (%d of %d)...\n',stations{ns},ns,nstations);

	% Create a list of arrival times for this station
	satime_indices = find(eqtimes(:,ns) > 0);
	satimes = eqtimes(satime_indices,ns); % Usually better practice to use logical indexing directly instead of a find(), but we need the indexes themselves for later

	% Calculate actual start and end times
	end_times = satimes + enddelay/86400;
	start_times = satimes - delay/86400;

	% Load waveforms for this station and channels
	wtmp = waveform(ds,scnl,start_times,end_times);

	% Reshape to a Nx1 vector; sometimes waveform() returns unusual
	% dimensions (???)
	wtmp = wtmp(:);

	% If 3-component data, rearrange to put channels together
	if is_tc
		wtmp = reshape(wtmp,size(wtmp,1)/3,3);
	end

	% Store event arrival times in waveform objects, then store the
	% waveforms in our big cell array eqwf.  There doesn't seem to be an
	% obvious way to vectorize these steps.
	% Also store other fields retrieved from database
	skip_count = 0;
	for na = 1:numel(satimes);

		% Check that the waveform is the one we expect; otherwise, skip to
		% the next origin
		wtime = get(wtmp(na-skip_count,1),'START');
		if abs(wtime - start_times(na)) > 0.1/86400
			skip_count = skip_count + 1;
			continue;
		end

		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'EVENT_START',satimes(na));
		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'ETYPE',cell2mat(etype(eqindices(satime_indices(na),ns))));
 		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'ORID',orid(eqindices(satime_indices(na),ns)));
		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'OTIME',otime(eqindices(satime_indices(na),ns)));
		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'SEAZ',seaz(eqindices(satime_indices(na),ns)));

		% Technically, there can be a different signal type for each picked
		% arrival.  Here we make the assumption that the value of stype for
		% the chosen iphase (typically P) applies to all components.  This
		% will need to be kept in mind if developing a classification
		% routine that makes use of stype, and training catalogues designed
		% accordingly.
		wtmp(na-skip_count,:) = addfield(wtmp(na-skip_count,:),'STYPE',stype(eqindices(satime_indices(na),ns)));

		% Store waveforms in cell array.
		eqwf{satime_indices(na),ns} = wtmp(na-skip_count,:);
	end
end

clear('wtmp'); % No longer needed so free up some space

%% Convert waveforms to threecomp objects where appropriate

% We've loaded all the waveforms.  Now convert the three-component sets
% into threecomp objects

fprintf(2,'Converting to threecomp objects if applicable...\n');

eqtc = cell(size(eqwf));
for no = 1:norigins
	for ns = 1:nstations
		if length(eqwf{no,ns}) == 3 % If it's three-compoenent
			try
				eqtc{no,ns} = threecomp(eqwf{no,ns},eqtimes(no,ns));
			catch me
				warning(['Error converting earthquake (' num2str(no) ',' num2str(ns) ') to threecomp object.  Leaving empty.']);
				eqtc{no,ns} = [];
			end
		else % If it's 1-component (or empty)...
			eqtc{no,ns} = eqwf{no,ns}; % ...store the original value
		end
	end
end

%% Return eqwf if it was requested

if ret_eqwf
	varargout{1} = eqwf;
end

fprintf(2,'Done.\n');

end


function match_files = getRegexFiles(path, regex)
% files = dir([path '/*.mat']);
files = dir(path);
names = {files.name};
match_indices = ~cellfun(@isempty, regexp(names, regex, 'match', 'once'));
match_files = names(match_indices);
end


function fh = isolate_axes(ah, vis)
%ISOLATE_AXES Isolate the specified axes in a figure on their own
%
% Examples:
%   fh = isolate_axes(ah)
%   fh = isolate_axes(ah, vis)
%
% This function will create a new figure containing the axes/uipanels
% specified, and also their associated legends and colorbars. The objects
% specified must all be in the same figure, but they will generally only be
% a subset of the objects in the figure.
%
% IN:
%    ah - An array of axes and uipanel handles, which must come from the
%         same figure.
%    vis - A boolean indicating whether the new figure should be visible.
%          Default: false.
%
% OUT:
%    fh - The handle of the created figure.

% Copyright (C) Oliver Woodford 2011-2013

% Thank you to Rosella Blatt for reporting a bug to do with axes in GUIs
% 16/03/12: Moved copyfig to its own function. Thanks to Bob Fratantonio
%           for pointing out that the function is also used in export_fig.m
% 12/12/12: Add support for isolating uipanels. Thanks to michael for suggesting it
% 08/10/13: Bug fix to allchildren suggested by Will Grant (many thanks!)
% 05/12/13: Bug fix to axes having different units. Thanks to Remington Reid for reporting
% 21/04/15: Bug fix for exporting uipanels with legend/colorbar on HG1 (reported by Alvaro
%           on FEX page as a comment on 24-Apr-2014); standardized indentation & help section
% 22/04/15: Bug fix: legends and colorbars were not exported when exporting axes handle in HG2

    % Make sure we have an array of handles
    if ~all(ishandle(ah))
        error('ah must be an array of handles');
    end
    % Check that the handles are all for axes or uipanels, and are all in the same figure
    fh = ancestor(ah(1), 'figure');
    nAx = numel(ah);
    for a = 1:nAx
        if ~ismember(get(ah(a), 'Type'), {'axes', 'uipanel'})
            error('All handles must be axes or uipanel handles.');
        end
        if ~isequal(ancestor(ah(a), 'figure'), fh)
            error('Axes must all come from the same figure.');
        end
    end
    % Tag the objects so we can find them in the copy
    old_tag = get(ah, 'Tag');
    if nAx == 1
        old_tag = {old_tag};
    end
    set(ah, 'Tag', 'ObjectToCopy');
    % Create a new figure exactly the same as the old one
    fh = copyfig(fh); %copyobj(fh, 0);
    if nargin < 2 || ~vis
        set(fh, 'Visible', 'off');
    end
    % Reset the object tags
    for a = 1:nAx
        set(ah(a), 'Tag', old_tag{a});
    end
    % Find the objects to save
    ah = findall(fh, 'Tag', 'ObjectToCopy');
    if numel(ah) ~= nAx
        close(fh);
        error('Incorrect number of objects found.');
    end
    % Set the axes tags to what they should be
    for a = 1:nAx
        set(ah(a), 'Tag', old_tag{a});
    end
    % Keep any legends and colorbars which overlap the subplots
    % Note: in HG1 these are axes objects; in HG2 they are separate objects, therefore we
    %       don't test for the type, only the tag (hopefully nobody but Matlab uses them!)
    lh = findall(fh, 'Tag', 'legend', '-or', 'Tag', 'Colorbar');
    nLeg = numel(lh);
    if nLeg > 0
        set([ah(:); lh(:)], 'Units', 'normalized');
        try
            ax_pos = get(ah, 'OuterPosition'); % axes and figures have the OuterPosition property
        catch
            ax_pos = get(ah, 'Position'); % uipanels only have Position, not OuterPosition
        end
        if nAx > 1
            ax_pos = cell2mat(ax_pos(:));
        end
        ax_pos(:,3:4) = ax_pos(:,3:4) + ax_pos(:,1:2);
        try
            leg_pos = get(lh, 'OuterPosition');
        catch
            leg_pos = get(lh, 'Position');  % No OuterPosition in HG2, only in HG1
        end
        if nLeg > 1;
            leg_pos = cell2mat(leg_pos);
        end
        leg_pos(:,3:4) = leg_pos(:,3:4) + leg_pos(:,1:2);
        ax_pos = shiftdim(ax_pos, -1);
        % Overlap test
        M = bsxfun(@lt, leg_pos(:,1), ax_pos(:,:,3)) & ...
            bsxfun(@lt, leg_pos(:,2), ax_pos(:,:,4)) & ...
            bsxfun(@gt, leg_pos(:,3), ax_pos(:,:,1)) & ...
            bsxfun(@gt, leg_pos(:,4), ax_pos(:,:,2));
        ah = [ah; lh(any(M, 2))];
    end
    % Get all the objects in the figure
    axs = findall(fh);
    % Delete everything except for the input objects and associated items
    delete(axs(~ismember(axs, [ah; allchildren(ah); allancestors(ah)])));
end

function ah = allchildren(ah)
    ah = findall(ah);
    if iscell(ah)
        ah = cell2mat(ah);
    end
    ah = ah(:);
end

function ph = allancestors(ah)
    ph = [];
    for a = 1:numel(ah)
        h = get(ah(a), 'parent');
        while h ~= 0
            ph = [ph; h];
            h = get(h, 'parent');
        end
    end
end


%This folder contains the so-called CONTROL functions for bbci_apply.
%These functions transform the classifier output into the control signal
%(PACKET), that will be sent to the application via UDP. The PACKET is
%formatted as a variable/value list in a CELL.
%
%These functions have the following format:
%
% --- --- --- ---
%BBCI_CONTROL_XYZ - Generate control signal for application XYZ
%
%Synopsis:
%  [PACKET, STATE]= bbci_control_XYZ(CFY_OUT, STATE, EVENT_INFO, <PARAMS>)
%
%Arguments:
%  CFY_OUT - Output of the classifier
%  STATE - Internal state variable, which is empty in the first call of a
%      run of bbci_apply.
%  EVENT_INFO - Structure that specifies the event (fields 'time' and 'desc')
%      that triggered the evaluation of this control. Furthermore, EVENT_INFO
%      contains the whole marker queue in the field 'all_markers'.
%  PARAMS - Additional parameters that can be specified in bbci.control.param
%      are passed as further arguments.
%
%Output:
% PACKET: Variable/value list in a CELL defining the control signal that
%     is to be sent via UDP to the application.
% STATE: Updated internal state variable
% --- --- --- ---
%
%
%List of CONTROL functions (prefix bbci_control_ is left out):
% ERPSpeller: ERP-based Hex-o-Spell, one output for each complete sequence
% ERPSpellerBinary: ERP-based Hex-o-Spell, one outputs for each stimulus


% VL_TEST_INIT  Intialize test file
%   This is a VLFeat internal function used to initialize
%   a file containing tests.
%
%   See also: VL_TEST(), VL_HELP().

% Author:: Andrea Vedaldi

% Copyright (C) 2007-12 Andrea Vedaldi and Brian Fulkerson.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

% obtain path to caller m-file
stack = dbstack(1) ;
mfilePath = stack(1).file ;
mfileName = stack(1).name ;
fid = fopen(mfilePath, 'r') ;

% Parse source code to get subfunction names. It could use
% which('-subfun', stack(1).name) instead, but it is undocumented.

setupFunc = [] ;
teardownFunc = [] ;

tests = {} ;
l = 0 ;
t = 0 ;
while 1
  tline = fgetl(fid) ;
  l = l + 1 ;
  if ~ischar(tline), break ; end
  mo = regexp(tline, '^\s*function\s+(\w\s*=\s*)?test_(?<name>[\w_]+)\s*\(.*\).*$', ...
              'names') ;
  if length(mo) > 0
    t = t + 1 ;
    tests{t}.name = mo(1).name ;
    tests{t}.func = str2func(['test_' mo(1).name]) ;
    tests{t}.lineNumber = l ;
  end

  mo = regexp(tline, '^\s*function\s+(\w+\s*=\s*)?(?<name>setup|teardown)\s*\(.*\).*$', ...
              'names') ;
  if length(mo) > 0
    switch mo(1).name
      case 'setup'
        if isempty(setupFunc)
          setupFunc = @setup ;
        else
          warning('%s:%d: ignoring redundant definition of the setup function', ...
                  mfilePath) ;
        end
      case 'teardown'
        if isempty(teardownFunc)
          teardownFunc = @teardown ;
        else
          warning('%s:%d: ignoring redundant definition of the setup function', ...
                  mfilePath) ;
        end
      otherwise
        assert(0) ;
    end
  end
end

if ~isempty(setupFunc), state = setupFunc() ; end

if length(varargin) > 0
  testFunc = str2func(sprintf('test_%s', varargin{1})) ;
  if exist('state','var')
    testFunc(state) ;
  else
    testFunc() ;
  end
  if ~isempty(teardownFunc), teardownFunc(state) ; end
  return ;
end

% Run all tests collecting the results.
if length(tests) == 0
  error('No tests found in ''%s''', mfileName)  ;
end

for t = 1:length(tests)
  subTestName = sprintf('test_%s', tests{t}.name) ;
  results{t}.testName = sprintf('%s:%s', mfileName(9:end), tests{t}.name) ;
  results{t}.succeded = true ;
  results{t}.exception = [] ;
  try
    if exist('state', 'var')
      tests{t}.func(state) ;
    else
      tests{t}.func() ;
    end
  catch exception
    results{t}.succeded = false ;
    results{t}.exception = exception ;
  end
end

if ~isempty(teardownFunc), teardownFunc(state) ; end

results = cat(2, results{:}) ;


function D = spm_eeg_convert(S)
% Convert various M/EEG formats to SPM12 format
% FORMAT D = spm_eeg_convert(S)
% S                - string (filename) or struct (see below)
%
% If S is a struct it can have the optional following fields:
% S.dataset        - file name
% S.mode           - 'header'     - only convert the header without reading data
%                    'continuous' - convert data as continuous
%                    'epoched'    - convert data as epoched (requires data that is
%                                   already epoched or a trial definition in S.trl).
% S.timewin        - for continuous mode [start end] of data segment in sec (all if empty)
%                  - for epoched mode time window in PST ms
% S.outfile        - output file name (default 'spmeeg_' + input)
% S.channels       - 'all' - convert all channels
%                    or cell array of labels
% For epoched mode:
%
% S.trl            - [N x 3] trl matrix or name of the trial definition file
%                    containing 'trl' variable with such a matrix
% S.conditionlabels- labels for the trials in the data [default: 'Undefined']
%
%   or
%
% S.trialdef       - structure array for trial definition with fields
%     S.trialdef.conditionlabel - string label for the condition
%     S.trialdef.eventtype      - string
%     S.trialdef.eventvalue     - string, numeric or empty
%
%
% S.inputformat    - data type (optional) to force the use of specific data
%                    reader
% S.eventpadding   - the additional time period around each trial for which
%                    the events are saved with the trial (to let the user
%                    keep and use for analysis events which are outside
%                    trial borders), in seconds. [default: 0]
% S.blocksize      - size of blocks used internally to split large files
%                    [default: ~100Mb]
% S.checkboundary  - 1 - check if there are breaks in the file and do not
%                        read across those breaks [default]
%                    0 - ignore breaks (not recommended).
% S.saveorigheader - 1 - save original data header with the dataset
%                    0 - do not keep the original header [default]
%
% % D              - MEEG object (also written on disk)
%__________________________________________________________________________
% Copyright (C) 2008-2017 Wellcome Trust Centre for Neuroimaging

% Vladimir Litvak
% $Id: spm_eeg_convert.m 7671 2019-10-02 12:34:50Z vladimir $

SVNrev = '$Rev: 7671 $';

%-Startup
%--------------------------------------------------------------------------
spm('FnBanner', mfilename, SVNrev);
spm('FigName','M/EEG convert'); spm('Pointer', 'Watch');

if ischar(S)
    temp      = S;
    S         = [];
    S.dataset = temp;
end

if ~isfield(S, 'dataset')
    error('Dataset must be specified.');
end

if ~isfield(S, 'outfile'),         S.outfile = ['spmeeg_' spm_file(S.dataset,'basename')]; end
if ~isfield(S, 'channels'),        S.channels = 'all';                                     end
if ~isfield(S, 'timewin'),         S.timewin   = [];                                       end
if ~isfield(S, 'blocksize'),       S.blocksize = 3276800;                                  end %100 Mb
if ~isfield(S, 'checkboundary'),   S.checkboundary = 1;                                    end
if ~isfield(S, 'eventpadding'),    S.eventpadding = 0;                                     end
if ~isfield(S, 'saveorigheader'),  S.saveorigheader = 0;                                   end
if ~isfield(S, 'conditionlabels'), S.conditionlabels = 'Undefined' ;                       end
if ~isfield(S, 'inputformat'),     S.inputformat = [] ;                                    end

if ~iscell(S.conditionlabels)
    S.conditionlabels = {S.conditionlabels};
end

if ~isfield(S, 'mode') || ~isequal(S.mode, 'header')
    % The header is read here in a recursive call and the above if avoids reading
    % it twice which might be expensive for some formats
    S1 = [];
    S1.mode           = 'header';
    S1.dataset        = S.dataset;
    S1.outfile        = S.outfile;
    S1.inputformat    = S.inputformat;
    Dhdr              = spm_eeg_convert(S1);
    hdr               = Dhdr.hdr;
    event             = Dhdr.events;
    eventsamples      = Dhdr.events(':', 'samples');
else
    %--------- Read and check header
    hdr = ft_read_header(S.dataset, 'headerformat', S.inputformat);

    if isfield(hdr, 'label')
        [unique_label,junk,ind] = unique(hdr.label);
        if length(unique_label) ~= length(hdr.label)
            warning(['Data file contains several channels with ',...
                'the same name. These channels cannot be processed and will be disregarded']);
            % This finds the repeating labels and removes all their occurences
            sortind     = sort(ind);
            [junk,ind2] = setdiff(hdr.label, unique_label(sortind(diff(sortind)==0)));
            hdr.label   = hdr.label(ind2);
            hdr.nChans  = length(hdr.label);
        end
    end

    %--------- Read and prepare events

    try
        event = ft_read_event(S.dataset, 'detectflank', 'both', 'eventformat', S.inputformat);

        if ~isempty(strmatch('UPPT001', hdr.label))
            % This is s somewhat ugly fix to the specific problem with event
            % coding in FIL CTF. It can also be useful for other CTF systems where the
            % pulses in the event channel go downwards.
            fil_ctf_events = ft_read_event(S.dataset, 'detectflank', 'down', 'type', 'UPPT001', 'trigshift', -1, 'eventformat', S.inputformat);
            if ~isempty(fil_ctf_events)
                [fil_ctf_events(:).type] = deal('FIL_UPPT001_down');
                event = cat(1, event(:), fil_ctf_events(:));
            end
        end

        if ~isempty(strmatch('UPPT002', hdr.label))
            % This is s somewhat ugly fix to the specific problem with event
            % coding in FIL CTF. It can also be useful for other CTF systems where the
            % pulses in the event channel go downwards.
            fil_ctf_events = ft_read_event(S.dataset, 'detectflank', 'down', 'type', 'UPPT002', 'trigshift', -1, 'eventformat', S.inputformat);
            if ~isempty(fil_ctf_events)
                [fil_ctf_events(:).type] = deal('FIL_UPPT002_down');
                event = cat(1, event(:), fil_ctf_events(:));
            end
        end

        % This is another FIL-specific fix that will hopefully not affect other sites
        if isfield(hdr, 'orig') && isfield(hdr.orig, 'VERSION') && isequal(uint8(hdr.orig.VERSION),uint8([255 'BIOSEMI']))
            ind = strcmp('STATUS', {event(:).type});
            val = [event(ind).value];
            if ~isempty(val)
                bytes  = dec2bin(val, 8);
                bytes  = bytes(:, end-7:end);
                % This is a very specific criterion that assumes that
                % trigger code 1 is always used.
                if ~ismember('00000001', bytes, 'rows') && ismember('10000000', bytes, 'rows')
                    bytes = fliplr(bytes);
                end
                nval   = bin2dec(bytes);
                if (sum(val(:)>nval(:))/length(val))>0.5
                    nval    = num2cell(nval);
                    [event(ind).value] = deal(nval{:});
                end
            end
        end

    catch
        warning(['Could not read events from file ' S.dataset]);
        event = [];
    end

    % Replace samples with time
    if numel(event)>0
        for i = 1:numel(event)
            event(i).time = event(i).sample./hdr.Fs;
        end
    end
end

if ~isfield(S, 'mode')
    if (hdr.nTrials == 1)
        S.mode = 'continuous';
    else
        S.mode = 'epoched';
    end
end

%--------- Start making the header

D = [];
D.Fsample = hdr.Fs;

%--------- Select channels

if ~strcmp(S.channels, 'all') %Dhdr should be available in this case
    chansel = selectchannels(Dhdr, S.channels);
else
    if isfield(hdr, 'nChans')
        chansel = 1:hdr.nChans;
    else
        chansel = 1:length(hdr.label);
    end
end

nchan = length(chansel);

D.channels = repmat(struct('bad', 0), 1, nchan);

if isfield(hdr, 'label')
    [D.channels(:).label] = deal(hdr.label{chansel});
end
%--------- Preparations specific to reading mode (continuous/epoched)

if ismember(S.mode, {'continuous', 'header'})

    if isempty(S.timewin)
        if hdr.nTrials == 1
            segmentbounds = [1 hdr.nSamples];
        elseif ~S.checkboundary || isequal(S.mode, 'header')
            segmentbounds = [1 hdr.nSamples*hdr.nTrials];
        else
            error('The data cannot be read without ignoring trial borders');
        end
        timewindow = segmentbounds./D.Fsample;
    else
        timewindow = S.timewin;
        segmentbounds = round(timewindow.*D.Fsample);
        segmentbounds(1) = max(segmentbounds(1), 1);
    end

    %--------- Sort events and put in the trial

    if ~isempty(event)
        try
            event = rmfield(event, {'offset', 'sample'});
        end
        event = select_events(event, ...
            [timewindow(1)-S.eventpadding timewindow(2)+S.eventpadding]);
    end

    D.trials.label  = S.conditionlabels{1};
    D.trials.events = event;
    D.trials.onset  = timewindow(1);

    %--------- Break too long segments into blocks

    nblocksamples = floor(S.blocksize/nchan);
    nsampl = diff(segmentbounds)+1;

    trl = segmentbounds(1):nblocksamples:segmentbounds(2);
    if (trl(end)==segmentbounds(2))
        trl = trl(1:(end-1));
    end

    trl = [trl(:) [trl(2:end)-1 segmentbounds(2)]'];

    ntrial = size(trl, 1);

    readbytrials = 0;

    D.timeOnset = (trl(1,1)-1)./hdr.Fs;

    D.Nsamples = nsampl;
else % Read by trials
    if isfield(S, 'trl') || isfield(S, 'trialdef')
        if isfield(S, 'trl')
            if ischar(S.trl)
                trl = getfield(load(S.trl, 'trl'), 'trl');
                conditionlabels = getfield(load(S.trl, 'conditionlabels'), 'conditionlabels');
            else
                trl = S.trl;
                conditionlabels = S.conditionlabels;
            end
        else
            S1          = [];
            S1.D        = Dhdr;
            S1.timewin  = S.timewin;
            S1.trialdef = S.trialdef;
            S1.reviewtrials = 0;
            S1.save  = 0;
            [trl, conditionlabels] = spm_eeg_definetrial(S1);
        end

        trl = double(trl);

        if size(trl, 2) >= 3
            D.timeOnset = unique(trl(:, 3))./D.Fsample;
            trl = trl(:, 1:2);
        else
            D.timeOnset = 0;
        end

        if length(D.timeOnset) > 1
            error('All trials should have identical baseline');
        end

        if ~iscell(conditionlabels)
            conditionlabels = {conditionlabels};
        end

        if numel(conditionlabels) == 1
            conditionlabels = repmat(conditionlabels, 1, size(trl, 1));
        end

        readbytrials = 0;
    else
        try
            trialind = sort([strmatch('trial', {event.type}, 'exact'), ...
                strmatch('average', {event.type}, 'exact')]);
            trl = [eventsamples(trialind).sample];
            trl = double(trl(:));
            trl = [trl  trl+double([event(trialind).duration]')-1];

            try
                offset = unique([event(trialind).offset]);
            catch
                offset = [];
            end

            if length(offset) == 1 && offset~=0
                D.timeOnset = offset/D.Fsample;
            else
                D.timeOnset = -hdr.nSamplesPre/hdr.Fs;
            end
            conditionlabels = {};
            for i = 1:length(trialind)
                if isempty(event(trialind(i)).value)
                    conditionlabels{i} = S.conditionlabels{1};
                else
                    if all(ischar(event(trialind(i)).value))
                        conditionlabels{i} = event(trialind(i)).value;
                    else
                        conditionlabels{i} = num2str(event(trialind(i)).value);
                    end
                end
            end
            if  hdr.nTrials>1 && size(trl, 1)~=hdr.nTrials
                warning('Mismatch between trial definition in events and in data. Ignoring events');
                readbytrials = 1;
            else
                readbytrials = 0;
            end

            event = event(setdiff(1:numel(event), trialind));
        catch
            if hdr.nTrials == 1
                error('Could not define trials based on data. Use continuous option or trial definition file.');
            else
                readbytrials = 1;
            end
        end
    end
    if readbytrials
        nsampl = hdr.nSamples;
        ntrial = hdr.nTrials;
        trl = zeros(ntrial, 2);
        if exist('conditionlabels', 'var') ~= 1 || length(conditionlabels) ~= ntrial
            conditionlabels = repmat(S.conditionlabels, 1, ntrial);
        end
    else
        nsampl = unique(diff(trl, [], 2))+1;
        if length(nsampl) > 1
            error('All trials should have identical lengths');
        end

        inbounds = (trl(:,1)>=1 & trl(:, 2)<=hdr.nSamples*hdr.nTrials)';

        rejected = find(~inbounds);

        if ~isempty(rejected)
            trl = trl(inbounds, :);
            conditionlabels = conditionlabels(inbounds);
            warning([S.dataset ': Trials ' num2str(rejected) ' not read - out of bounds']);
        end

        ntrial = size(trl, 1);

        if ntrial == 0
            warning([S.dataset ': No trials to read. Bailing out.']);
            D = [];
            return;
        end
    end
    D.Nsamples = nsampl;
    if isfield(event, 'sample')
        event = rmfield(event, 'sample');
    end
end

%--------- Prepare for reading the data
outpath = spm_file(S.outfile,'fpath');
outfile = spm_file(S.outfile,'basename');
if isempty(outfile), outfile = 'spmeeg'; end

D.path = outpath;
D.fname = [outfile '.mat'];

if ~isequal(S.mode, 'header')

    % These are the units used for channel data in SPM
    chanunit  = units(Dhdr, chansel);
    chanunit(strcmp('T',   chanunit)) = {'fT'};
    chanunit(strcmp('T/m', chanunit)) = {'fT/mm'};
    chanunit(strcmp('V',   chanunit)) = {'uV'};

    if isequal(S.mode, 'continuous')
        D.data = file_array(fullfile(D.path, [outfile '.dat']), [nchan nsampl], 'float32-le');
    else
        D.data = file_array(fullfile(D.path, [outfile '.dat']), [nchan nsampl ntrial], 'float32-le');
    end

    % physically initialise file
    initialise(D.data);

    spm_progress_bar('Init', ntrial, 'reading and converting'); drawnow;
    if ntrial > 100, Ibar = floor(linspace(1, ntrial,100));
    else Ibar = 1:ntrial; end

    %--------- Read the data

    offset = 1;
    for i = 1:ntrial
        spm_progress_bar('Set','ylabel','reading...');
        if readbytrials
            dat = ft_read_data(S.dataset,'header',  hdr, 'begtrial', i, 'endtrial', i,...
                'chanindx', chansel, 'chanunit', chanunit, 'checkboundary', S.checkboundary, 'dataformat', S.inputformat);
        else
            dat = ft_read_data(S.dataset,'header',  hdr, 'begsample', trl(i, 1), 'endsample', trl(i, 2),...
                'chanindx', chansel, 'chanunit', chanunit, 'checkboundary', S.checkboundary, 'dataformat', S.inputformat);
        end

        % Sometimes ft_read_data returns sparse output
        dat = full(dat);

        spm_progress_bar('Set','ylabel','writing...');
        switch S.mode
            case 'continuous'
                nblocksamples = size(dat,2);

                D.data(:, offset:(offset+nblocksamples-1)) = dat;

                offset = offset+nblocksamples;
            case 'epoched'
                D.data(:, :, i) = dat;
                D.trials(i).label = conditionlabels{i};
                D.trials(i).onset = trl(i, 1)./D.Fsample;
                D.trials(i).events = select_events(event, ...
                    [ trl(i, 1)./D.Fsample-S.eventpadding  trl(i, 2)./D.Fsample+S.eventpadding]);
        end

        if ismember(i, Ibar)
            spm_progress_bar('Set', i);
        end

    end

    spm_progress_bar('Clear');

else
    D.data = [];
end

% Specify sensor positions and fiducials
if isfield(hdr, 'grad')
    D.sensors.meg = hdr.grad;
end

if isfield(hdr, 'elec')
    elec = hdr.elec;
else
    try
        elec = ft_read_sens(S.dataset, 'fileformat', S.inputformat);
        % It might be that read_sens will return the grad for MEG datasets
        if any(ismember({'ori', 'coilori', 'coilpos'}, fieldnames(elec)))
            elec = [];
        end
    catch
        elec = [];
    end
end

if ~isempty(elec)
    D.sensors.eeg = elec;
else
    sw = warning('off','backtrace');
    warning('Could not obtain electrode locations automatically.');
    warning(sw);
end

try
    D.fiducials = ft_convert_units(ft_read_headshape(S.dataset, 'fileformat', S.inputformat), 'mm');
catch
    sw = warning('off','backtrace');
    warning('Could not obtain fiducials automatically.');
    warning(sw);
end

%--------- Create meeg object
D = meeg(D);

% history
D = D.history('spm_eeg_convert', S);

if isfield(hdr, 'orig')
    if S.saveorigheader
        D.origheader = hdr.orig;
    end

    % Uses fileio function to get the information about channel types stored in
    % the original header.
    origchantypes = ft_chantype(hdr);
    [sel1, sel2] = spm_match_str(D.chanlabels, hdr.label);
    origchantypes = origchantypes(sel2);
    if length(strmatch('unknown', origchantypes, 'exact')) ~= numel(origchantypes)
        D.origchantypes = struct([]);
        D.origchantypes(1).label = hdr.label(sel2);
        D.origchantypes(1).type = origchantypes;
    end
end

S1 = [];
S1.task = 'defaulttype';
S1.D = D;
S1.updatehistory = 0;
D = spm_eeg_prep(S1);

% Assign default EEG sensor positions if possible
if ~isempty(strmatch('EEG', D.chantype, 'exact'))
    if isempty(D.sensors('EEG'))
        S1 = [];
        S1.task = 'defaulteegsens';
        S1.updatehistory = 0;
        S1.D = D;

        D = spm_eeg_prep(S1);
    else
        S1 = [];
        S1.task = 'project3D';
        S1.modality = 'EEG';
        S1.updatehistory = 0;
        S1.D = D;

        D = spm_eeg_prep(S1);
    end
end

% Create 2D positions for MEG
% by projecting the 3D positions to 2D
if ~isempty(strmatch('MEG', D.chantype)) && ~isempty(D.sensors('MEG'))
    S1 = [];
    S1.task = 'project3D';
    S1.modality = 'MEG';
    S1.updatehistory = 0;
    S1.D = D;

    D = spm_eeg_prep(S1);
end

% If channel units are available, store them.
if isequal(S.mode, 'header')
    [sel1, sel2] = spm_match_str(D.chanlabels, hdr.label);
    D = units(D, sel1, hdr.chanunit(sel2));
else
    D = units(D, ':', chanunit);
end

% The conditions will later be sorted in the original order they were defined.
if isfield(S, 'trialdef')
    D = condlist(D, {S.trialdef(:).conditionlabel});
end

% This is for the recursive call to work properly
if isequal(S.mode, 'header')
    D.hdr = hdr;
end

if ~isequal(S.mode, 'header')
    save(D);
end

%-Cleanup
%--------------------------------------------------------------------------
fprintf('%-40s: %30s\n','Completed',spm('time'));                       %-#
spm('FigName','M/EEG convert: done'); spm('Pointer', 'Arrow');

%==========================================================================
% select_events
%==========================================================================
function event = select_events(event, timeseg)
% Utility function to select events according to time segment
% FORMAT event = select_events(event, timeseg)
if iscell(event)
    event = event{1};
end

if ~isempty(event)
    [time,ind] = sort([event(:).time]);

    selectind  = ind(time>=timeseg(1) & time<=timeseg(2));

    event      = event(selectind);
end