%qsubfunc() - submit a job to the Sun Grid Engine queue system, 
%             version 1.00, 25 Oct 2002
% Usage:
%   >> qsubfunc( funcname, funcarg);
%   >> status = qsubfunc( funcname, funcarg, funcout, 'key', 'val' );
%   >> status = qsubfunc({{ funcname1, funcarg1, funcout1, 'key', 'val' ... } ...
%                         { funcname2, funcarg2, funcout2, 'key', 'val' ... }});
%
% Inputs:
%   funcname       - string for the function name. Can also be a cell array
%                    of cell arrays of calls to qsubfunc() (see example).
%   funcarg        - cell array of the function arguments
%   funcout        - cell array of output variables in the global
%                    workspace (as strings). Default is [].
%
% Optional inputs:
%   'display'   - ['on'|'off'] Send back display to original
%                 X window server. Default is 'on'.
%   'jobname'   - ['string'] Name of the job. Default is 
%                 'qsub[funcname][XXXX]' where XXXX is a random number 
%   'mail'      - ['on'|'off'] Send mail for job events (job beginning, ...
%                 end, abort, suspend). Default is 'off'.
%   'mailabord' - ['on'|'off'] Send mail only for job problems such as
%                 when the job is aborted or suspended. Default is 'on'.
%   'nice'      - [integer] Renice the job. Default is -15.
%   'comsave'   - [command] Instead of displaying the figure, the
%                 'comsave' command is executed. Note that this can be
%                 use in conjunction with 'display'. By default, when 
%                 the 'comsave' command is not empty, the 'display' is
%                 set to 'off', but you can force it to 'on' to save 'jpg'
%                 figures, for examppe, which requires the X-SERVER to be 
%                 functional (though not required for printing '.eps' files 
%                 or for using the hgsave() function).
%                 Ex: 'print -depsc /home/user/test.eps'
%   'hostname'  - [string] Force hostname for display 
%   'processes' - Cell array of other parallel function to launch. The
%                 syntax is the same as the qsubfunction. This is only
%                 useful if the called functions return some values
%                 (otherwise it is possible to launch all the commands
%                 sequentially).
%   'path'      - [string] folder to store information for function calls.
%                 By default it is a directory named "qsub" in the home 
%                 directory (folder is created if it does not exist). Use
%                 absolute paths.
%
% Outputs: status - [0|1] 0=fail, 1=suceeded to submit job
%  
% Example:
%
%   % Submit a single plotting job
%   qsubfunc('plot', { [0:10] }); 
%
%   % Submit 4 plotting jobs and return
%   >> qsubfunc({{'plot', { [0:10]  'r' }}, { 'plot', { [10:20] 'm' } } ...
%            { 'plot', { [20:30] 'g' } } { 'plot', { [30:40] 'y' } }} );
%
%   % Submit 2 compute jobs and pop-up an editing window to enter commands 
%   % in the local workspace while the jobs are running
%   >> qsubfunc({ {'max', { [1:10,11:-1:1] }, { 'maxval' 'maxindex' }} ...
%              {'min', { [1:10,11:-1:1] }, { 'minval' 'minindex' }}} );
%   % The two jobs are strictly equivalent to the evaluation of the 
%   % following expression in the global workspace
%   >> [maxval maxindex] = max([1:10,11:-1:1]);
%   >> [minval minindex] = min([1:10,11:-1:1]);
%
% Note:
%   qsubfunc() will create a directory name "qsub" in your home direcoty,
%   then create sub-folders in this directory (these folders may have to be
%   removed manually).
%   Recursive calls to qsubfunc() from the qsubfunc edit window are possible.
%
% Graphical interface:
%   Execute a command: When jobs returns values to the global workspace, 
%   qsubfunc() has to wait for them (to write a file) and an editing 
%   window pops-up for the user to enter local commands to execute. To 
%   execute a command, type in some text and press the "Exectute button". 
%   Abort computation: it is also possible to abort the computation from the local
%   Matlab session by pressing the abort button (Note: jobs sent to the
%   grid are not automatically killed and have to be killed manually using
%   the command 'qdel' under Unix).
%   Close window: When computation is finished, the Abort button becomes
%   inactivated and the "Ready to close" button becomes activated. The window
%   does not close automatically, since the user might be editing some text.
%  
% Troubleshooting:
%   The function has been tested on Matlab 6.1 under Redhat Linux 2.4.19
%   using Sun Grid Engine 5.2 and tcsh shell. 
%   1) Problems may arise with use of bash.
%   2) Check that the nice command is accessible in /bin/nice otherwise
%      edit the code and enter the location of the nice command (or simply
%      "nice").
%   3) For any other problem, contact arno@salk.edu with a detailed 
%      description of the problem.
%
% Version:
%   The latest version of this function is available at 
%   www.sccn.ucsd.edu/~arno/qsubfunc/qsubfunc.php
%
% Authors: Arnaud Delorme, CNL / Salk Institute, 11 Oct 2002
%          (Joern Anemueller for the Unix qsub call syntax)
%
% See also: eeglab()

%123456789012345678901234567890123456789012345678901234567890123456789012

% Copyright (C) 2002 Arnaud Delorme & Jorn Anemuller, Salk Institute, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% $Log: qsubfunc.m,v $
% Revision 1.31  2005/03/22 17:28:16  arno
% debugging some calls
%
% Revision 1.30  2003/04/15 22:20:17  arno
% adding path option
%
% Revision 1.29  2003/04/15 22:04:05  arno
% adding csh command
%
% Revision 1.28  2003/03/15 20:42:46  scott
% header edit -sm
%
% Revision 1.27  2002/11/15 01:42:24  scott
% Can not -> cannot
%
% Revision 1.26  2002/11/01 19:51:52  arno
% putting back error fle check
%
% Revision 1.25  2002/11/01 18:35:33  arno
% removing .e and .o file checking -> appear at once
%
% Revision 1.24  2002/11/01 18:22:28  arno
% disble file deletion
%
% Revision 1.23  2002/11/01 18:09:26  arno
% dealing with qsub error messages
%
% Revision 1.22  2002/11/01 17:04:16  arno
% force hostname
%
% Revision 1.21  2002/11/01 00:58:23  arno
% go back to the original directory for writing
%
% Revision 1.20  2002/10/31 22:42:26  arno
% back to previous dir
%
% Revision 1.19  2002/10/25 16:41:51  arno
% add quesdlg2 and more doc
%
% Revision 1.18  2002/10/25 01:59:33  arno
% adding finputcheck code
%
% Revision 1.17  2002/10/25 01:55:47  arno
% including supergui in qsubfunc
%
% Revision 1.16  2002/10/25 00:41:34  arno
% nice update
%
% Revision 1.15  2002/10/25 00:35:14  arno
% debug command call
%
% Revision 1.14  2002/10/25 00:32:14  arno
% job index corrected
%
% Revision 1.13  2002/10/25 00:28:10  arno
% more debuging
%
% Revision 1.12  2002/10/25 00:20:43  arno
% update qsubfunc for real parallel processing
%
% Revision 1.11  2002/10/24 22:18:44  arno
% remove directory
%
% Revision 1.10  2002/10/24 20:13:40  arno
% remove comment
%
% Revision 1.9  2002/10/24 20:12:47  arno
% new qsubfunc mutiprocess
%
% Revision 1.8  2002/10/21 18:52:17  arno
% nice update
%
% Revision 1.7  2002/10/21 17:30:57  arno
% reset random generator
%
% Revision 1.6  2002/10/14 01:05:40  arno
% xhost +
%
% Revision 1.5  2002/10/13 23:26:17  arno
% putting path to the UNIX call
%
% Revision 1.4  2002/10/11 18:14:59  arno
% new options
%
% Revision 1.3  2002/10/11 17:26:37  arno
% debugging display
%
% Revision 1.2  2002/10/11 17:05:49  arno
% real intial revision
%

% note for how to return values
% I looked thrgh all the documentation, etc and there is 2 solutions
% 1 - write my own editor, so that the user is still able to evaluate expressions.
%      I can make a simple edit box and scroll the text up dan down, while using
%      the global workspace.
% 2 - start asynchronously mex commands

function status = qsubfunc( funcname, funcarg, funcout, varargin );
 
status = 0;
if nargin < 1
    help qsubfunc;
    return;
end;
if nargin < 2
    funcarg = {};
end;
if nargin < 3
    funcout = {};
end;

if iscell(funcname)
    oldd = pwd;
    if length(funcname{1}) == 2
        qsubfunc(funcname{1}{:}, {}, 'processes', funcname(2:end));
    elseif length(funcname{1}) == 3
        qsubfunc(funcname{1}{:}, 'processes', funcname(2:end));
    else
        error('Wrong number of arguments for first function call');
    end;
    %qsubfunc(funcname{1}{:}, 'processes', funcname(2:end));
    cd(oldd);
    return;
end;

% check inputs
% ------------
g = finputcheck(varargin, { 'display'   'string' { 'on' 'off' }     'on'; ...
                            'jobname'   'string' []                 ''; ...
                            'mail'      'string' { 'on' 'off' }     'off'; ...
                            'jobindex'  'integer' []                1; ...
                            'hostname'  'string' []                 ''; ...
                            'nice'      'integer' []                -15; ...
                            'path'      'string' []                 '~/qsub'; ...
                            'mailabord' 'string' { 'on' 'off' }     'on'; ...
                            'processes' 'cell'   []                 {}; ...
                            'comsave'   'string' []                 ''}, 'qsubfunc');
if isstr(g), error(g); end;

if g.path(end) == '/', g.path = g.path(1:end-1); end;
if isempty(g.jobname)
    g.jobname = sprintf('qsub%s%5.5d', funcname, floor(rand*100000));
end;
rand('state',sum(100*clock));
if ~isstr(funcname)
    disp('Error qsubfunc arguments: funcname must be a string, call aborded');
    return;
end;
if ~isempty(funcarg)
    if ~iscell(funcout)
        disp('Error qsubfunc arguments: funcarg must be a cell array, call aborded');
        return;
    end;
end;
if ~isempty(funcout)
    if ~iscell(funcout)
        disp('Error qsubfunc arguments: funcout must be a cell array, call aborded');
        return;
    else 
        for index = 1:length(funcout)
            if ~isstr(funcout{index})
                disp('Error qsubfunc arguments: funcout must be a cell array of strings, call aborded');
                return;
            end;
        end;
    end;
end;

% copy param file to directory
% ----------------------------
oldd = pwd;
cd ~
[res tmp] = unix([ 'mkdir ' g.path ]);
cd(g.path)
count = 10; res = 0;
fprintf('********************** Processing job %d ***********************\n', g.jobindex);
while res ~= 1 | count == 0
    fprintf('Atempting to create directory ''%s'': ', g.jobname);
    [res tmp] = mkdir(g.jobname);
    if res ~= 1, 
        fprintf('failure\n');
        g.jobname = sprintf('qsub%s%5.5d', funcname, floor(rand*100000));
    else    
        fprintf('OK\n');
        cd(g.jobname);
    end;
    coutn = count-1;
end;
if count == 0, cd(olld); error('Cannot create new directory'); end;
save('funcarg.mat', '-mat', 'funcarg');

% prepare function outputs
% ------------------------
argsout = '';
saveout = '';
for index = 1:length(funcout)
    argsout = [ argsout ' out' int2str(index) ];
    saveout = [ saveout '''out' int2str(index) ''','];
end;
if ~isempty(argsout), argsout = [ '[' argsout '] =' ]; end;

% make and execute command
% ------------------------
matlabcom = [ 'load(''funcarg.mat'');' ...
              'lasterr(''''); try,' argsout funcname '(funcarg{:}); lasterr(''''); catch, end;'];
if isempty(g.hostname) 
    [tmp g.hostname] = unix('hostname');
end;
matlabopt = '-nosplash -nodesktop';
if strcmpi(g.display, 'off')
    matlabopt = [ matlabopt ' -nodisplay' ];
else
    if g.jobindex == 1
        unix('xhost +');
    end;
    if isempty(g.comsave)
        matlabcom =[matlabcom 'if ~isempty(get(0, ''currentfigure'')); waitfor(gcf); end;' ];
    end;
end;
if ~isempty(argsout)
    matlabcom = [ matlabcom 'cd ' g.path '/' g.jobname ';funcerr = lasterr; if isempty(funcerr), save(''funcout.mat'', ''-mat'',' saveout ' ''funcerr'');'  ...
                 'else, save(''funcout.mat'', ''funcerr''); end;' ];
end;
    
matlabcom =[matlabcom g.comsave ];
unixcom = [ '/bin/csh; setenv DISPLAY ' g.hostname(1:end-1) ':0.0;' ...
            'cd ' g.path '/' g.jobname ';' ...
            '/bin/nice ' int2str(g.nice) ' matlab ' ...
            matlabopt ' -r \042' matlabcom '\042;'];
if isempty(funcout)
    %unixcom = [ unixcom 'cd ..;rm -f -r ' g.jobname];
    unixcom = [ unixcom 'cd ..;'];
end;

if strcmp(g.mail, 'on'),          qsubopt =  '-m beas';
elseif strcmp(g.mailabord, 'on'), qsubopt =  '-m as';
else                              qsubopt ='';
end;
strCmd = ['echo "' unixcom '" | qsub ' qsubopt ' -soft -l mem_total=3.5G,speed=1700 -N "' g.jobname '"'];
%fprintf('Executing command:\n%s\n\n', strCmd);
unix(strCmd);

% create editing window
% ---------------------
windowcreator = 0;
fig = findobj('tag', 'qsubfunc');
if isempty(fig)
    fig = figure('visible', 'off', 'tag', 'qsubfunc', 'userdata', []);
    abordcom =  [ 'if strcmpi(questdlg2(''All pending data will be lost'', ''Cancel'', ''Ok'', ''Ok''), ''ok''),' ...
                  'set(gcbf, ''userdata'', -1); end;' ];
    execcom  =  [ 'disp(get(findobj(gcbf, ''tag'', ''strcommand''), ''string''));' ...
                  'evalin(''base'', get(findobj(gcbf, ''tag'', ''strcommand''), ''string''), ''disp(lasterr);''); drawnow;']; 
    supergui( fig, [1 1 1 3], [1 4 1 1], {'style' 'text' 'string', 'Enter matlab command below                                                                  ' }, ...
              {'style' 'edit' 'string', '', 'tag', 'strcommand', 'HorizontalAlignment', 'left' }, ...
              {'style' 'text' 'string', 'Job running (only includes job returning outputs): 1', 'tag', 'jobrun' }, ...
              {'style' 'pushbutton' 'string', 'Abord', 'tag', 'abord', 'callback', abordcom }, ...
              {'style' 'pushbutton' 'string', 'Ready to close', 'tag', 'close', 'enable', 'off', 'callback', 'close(gcbf);' }, ...
              {'style' 'pushbutton' 'string', 'Execute', 'callback', execcom } );
    set(gcf, 'visible', 'off');
    windowcreator = 1;
else 
    objjob = findobj(fig, 'tag', 'jobrun');
    curjobs = get(fig, 'userdata');
    if length(curjobs) == 0
        windowcreator = 1;
    end;
end;
if ~isempty(funcout)
    curjobs = get(fig, 'userdata');
    curjobs(end+1).proc  = g.jobindex;
    curjobs(end).folder  = g.jobname;
    curjobs(end).funcout = funcout;
    set(fig, 'userdata', curjobs);

    objjob = findobj(fig, 'tag', 'jobrun');
    set(objjob, 'string', ['Job running (only includes job returning outputs): ' , int2str(length(curjobs)) ] );
    drawnow;
    set(findobj(fig, 'tag', 'abord'), 'enable', 'on'); 
    set(findobj(fig, 'tag', 'close'), 'enable', 'off'); % do not allow window to close
end;

% deal with other processes to launch
% -----------------------------------
if ~isempty(g.processes)
    cd(oldd);
    for index = 1:length(g.processes)
        if length(g.processes{index}) == 2
            qsubfunc(g.processes{index}{:}, {}, 'jobindex', g.jobindex+index);
        elseif length(g.processes{index}) == 3
            qsubfunc(g.processes{index}{:}, 'jobindex', g.jobindex+index);
        else
            error([ 'Wrong number of arguments for function in process ' int2str(index+1) ]);
        end;       
        %qsubfunc(g.processes{index}{:}, 'jobindex', g.jobindex+index);
    end;
end;

% waiting for outputs and processing them
% ---------------------------------------
if windowcreator
    fig = findobj('tag', 'qsubfunc');
    pendingjobs = length(get(fig, 'userdata'));
    if pendingjobs == 0 
        cleanup;
        return;
    else 
        set(fig, 'visible', 'on');
        drawnow;
    end;
    cd(g.path);
    while length(pendingjobs) > 0
        pause(0.5);
        pendingjobs = get(fig, 'userdata');
        if ~isstruct(pendingjobs) % abord
            close(fig);
            disp('Warning: jobs might still be running on other machines');
            pendingjobs = [];
        end;
        index = 1;
        while index <= length(pendingjobs)
            tmpdir = dir([ '~/' pendingjobs(index).folder '.e*']);
            if (~isempty(tmpdir) & tmpdir(1).bytes ~= 0) | exist([ pendingjobs(index).folder '/funcout.mat' ])
 
                % reading outputs to global workspace
                % -----------------------------------
                if exist([ pendingjobs(index).folder '/funcout.mat' ])
                    evalin('base', [ 'load(''' pendingjobs(index).folder '/funcout.mat'');' ]);
                else
                    evalin('base', [ 'tmpfid = fopen(''~/' tmpdir(1).name ''', ''r'');' ...
                                     'funcerr = '''';' ...
                                     'while ~feof(tmpfid), funcerr = [ funcerr fgets(tmpfid) ]; end;' ...
                                     'fclose(tmpfid);' ]);
                end;
                
                % cleaning up
                % -----------
                tmpdir = dir([ '~/' pendingjobs(index).folder '.*']);
                %unix(['rm -f ~/' tmpdir(1).name ]);
                %unix(['rm -f ~/' tmpdir(2).name ]);
                %unix(['rm -f -r ' pendingjobs(index).folder ]);
                
                if evalin('base', '~isempty(funcerr)')
                    evalin('base', 'funcerr = sprintf(''\nError while calling process using qsub:\n\n%s'', funcerr); disp(funcerr);');
                else
                    funcout = pendingjobs(index).funcout;
                    fprintf('Process %d: Loading ''%s'' function return variables ''%s'' ...\n', pendingjobs(index).proc, funcname, funcout{1});
                    for indexout = 1:length(funcout)
                        evalin('base', [ funcout{indexout} ' = out' int2str(indexout) ';' ], 'disp(lasterr);');
                        evalin('base'  , ['clear out' int2str(indexout) ]);
                        warning on;
                    end;
                end;
                evalin('base', 'clear funcerr;');
                
                % update gui window
                % -----------------
                pendingjobs(index) = [];
                set(fig, 'userdata', pendingjobs);
                objjob = findobj(fig, 'tag', 'jobrun');
                set(objjob, 'string', ['Jobs running (only includes jobs returning outputs): ' int2str(length(pendingjobs))] );
                drawnow;
                
            end;
            index = index + 1;
        end;
    end; % end while

    cleanup;
end;
cd(oldd); 
status = 1;
return;


function cleanup;
    fig = findobj('tag', 'qsubfunc');
    if ~isempty(fig)
        set(findobj(fig, 'tag', 'abord'), 'enable', 'off'); % allow window to close
        set(findobj(fig, 'tag', 'close'), 'enable', 'on'); % allow window to close
    end;

% questdlg2() - questdlg function clone with coloring and help for 
%               eeglab().
%
% Usage: same as questdlg()
%
% Warning: 
% Case of button text and result might be changed by the function
%         
%
% Author: Arnaud Delorme, CNL / Salk Institute, La Jolla, 11 August 2002
%
% See also: inputdlg2(), errordlg2(), supergui(), inputgui()

%123456789012345678901234567890123456789012345678901234567890123456789012

% Copyright (C) Arnaud Delorme, CNL / Salk Institute, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% $Log: qsubfunc.m,v $
% Revision 1.31  2005/03/22 17:28:16  arno
% debugging some calls
%
% Revision 1.30  2003/04/15 22:20:17  arno
% adding path option
%
% Revision 1.29  2003/04/15 22:04:05  arno
% adding csh command
%
% Revision 1.28  2003/03/15 20:42:46  scott
% header edit -sm
%
% Revision 1.27  2002/11/15 01:42:24  scott
% Can not -> cannot
%
% Revision 1.26  2002/11/01 19:51:52  arno
% putting back error fle check
%
% Revision 1.25  2002/11/01 18:35:33  arno
% removing .e and .o file checking -> appear at once
%
% Revision 1.24  2002/11/01 18:22:28  arno
% disble file deletion
%
% Revision 1.23  2002/11/01 18:09:26  arno
% dealing with qsub error messages
%
% Revision 1.22  2002/11/01 17:04:16  arno
% force hostname
%
% Revision 1.21  2002/11/01 00:58:23  arno
% go back to the original directory for writing
%
% Revision 1.20  2002/10/31 22:42:26  arno
% back to previous dir
%
% Revision 1.19  2002/10/25 16:41:51  arno
% add quesdlg2 and more doc
%
% Revision 1.11  2002/10/15 17:23:50  arno
% drawnow
%
% Revision 1.10  2002/10/15 16:54:09  arno
% add drawnow ofr windows
%
% Revision 1.9  2002/10/15 14:45:12  arno
% display warning
%
% Revision 1.8  2002/10/15 14:35:52  arno
% default case for buttons
%
% Revision 1.7  2002/08/14 18:17:00  arno
% new supergui arg
%
% Revision 1.6  2002/08/13 22:36:43  arno
% debug for error
%
% Revision 1.5  2002/08/13 17:29:35  arno
% new superguicall
%
% Revision 1.4  2002/08/12 18:49:20  arno
% header
%
% Revision 1.3  2002/08/12 18:24:29  arno
% debug
%
% Revision 1.2  2002/08/12 18:02:47  arno
% debug
%
% Revision 1.1  2002/08/12 18:01:34  arno
% Initial revision
%

function [result] = questdlg2(Prompt,Title,varargin);

result = varargin{end};
if nargin < 2
   help questdlg2;
   return;
end;
if Prompt(end) == 10, Prompt(end) = []; end;
if Prompt(end) == 10, Prompt(end) = []; end;
if Prompt(end) == 10, Prompt(end) = []; end;
if Prompt(end) == 10, Prompt(end) = []; end;

fig = figure('visible', 'off');
set(gcf, 'name', Title);

geometry = {[1]};
listui = {{ 'Style', 'text', 'string' Prompt }};

geometry = { geometry{:} ones(1,length(varargin)-1) };
for index = 1:length(varargin)-1 % ignoring default val
	listui = {listui{:} { 'Style', 'pushbutton', 'string', varargin{index}, 'callback', ['set(gcbf, ''userdata'', ''' varargin{index} ''');'] }  };
	if strcmp(varargin{index}, varargin{end})
		listui{end}{end+1} = 'fontweight';
		listui{end}{end+1} = 'bold';
	end;
end;

cr = length(find(Prompt == char(10)))+1;
if cr == 1
	cr = size(Prompt,1);
end;

[tmp tmp2 allobj] = supergui( fig, geometry, [cr 1], listui{:} );

waitfor( fig, 'userdata');
try,
	result = get(fig, 'userdata');
	close(fig);
    drawnow;
end;

% supergui() - a comprehensive gui automatic builder. This function help
%              to create GUI very fast without bothering about the 
%              positions of the elements. After creating a geometry, 
%              elements just place themselves into the predefined 
%              locations. It is especially usefull for figure where you
%              intend to put text button and descriptions.
%
% Usage:
%   >> [handlers, width, height ] = ...
%             supergui( geomx, geomy, { arguments1 }, { arguments2 }... );
% 
% Inputs:
%   figh    - figure handler, if 0 create a new figure
%   geomx   - cell array describing the geometry of the elements
%             in the figure. For instance, [2 3 2] means that the
%             figures will have 3 rows, with 2 elements in the first
%             and last row and 3 elements in the second row.
%             An other syntax is { [2 8] [1 2 3] } which means
%             that figures will have 2 rows, the first one with 2
%             elements of relative width 2 and 8 (20% and 80%). The
%             second row will have 3 elements of relative size 1, 2 
%             and 3.
%   geomy  - [array] describting geometry for the rows. For instance
%            [1 2 1] means that the second row will be twice the height
%            of the other ones. If [], all the lines have the same height.
%  {argument} - GUI matlab element arguments. Ex { 'style', 
%               'radiobutton', 'String', 'hello' }.
%
% Hint:
%    use 'print -mfile filemane' to save a matlab file of the figure.
%
% Output:
%    handlers  - all the handler of the elements (in the same form as the
%                geometry cell input.
%    height    - adviced widht for the figure (so the text look nice).   
%    height    - adviced height for the figure (so the text look nice).   
%
% Example:
%    figure;   
%    supergui( [1 1], [], { 'style', 'radiobutton', 'string', 'radio' }, ...
%        { 'style', 'pushbutton', 'string', 'push' });
%      
% Author: Arnaud Delorme, CNL / Salk Institute, La Jolla, 2001
%
% See also: eeglab()

%123456789012345678901234567890123456789012345678901234567890123456789012

% Copyright (C) 2001 Arnaud Delorme, Salk Institute, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% $Log: qsubfunc.m,v $
% Revision 1.31  2005/03/22 17:28:16  arno
% debugging some calls
%
% Revision 1.30  2003/04/15 22:20:17  arno
% adding path option
%
% Revision 1.29  2003/04/15 22:04:05  arno
% adding csh command
%
% Revision 1.28  2003/03/15 20:42:46  scott
% header edit -sm
%
% Revision 1.27  2002/11/15 01:42:24  scott
% Can not -> cannot
%
% Revision 1.26  2002/11/01 19:51:52  arno
% putting back error fle check
%
% Revision 1.25  2002/11/01 18:35:33  arno
% removing .e and .o file checking -> appear at once
%
% Revision 1.24  2002/11/01 18:22:28  arno
% disble file deletion
%
% Revision 1.23  2002/11/01 18:09:26  arno
% dealing with qsub error messages
%
% Revision 1.22  2002/11/01 17:04:16  arno
% force hostname
%
% Revision 1.21  2002/11/01 00:58:23  arno
% go back to the original directory for writing
%
% Revision 1.20  2002/10/31 22:42:26  arno
% back to previous dir
%
% Revision 1.19  2002/10/25 16:41:51  arno
% add quesdlg2 and more doc
%
% Revision 1.18  2002/10/25 01:59:33  arno
% adding finputcheck code
%
% Revision 1.17  2002/10/25 01:55:47  arno
% including supergui in qsubfunc
%
% Revision 1.34  2002/10/23 15:06:40  arno
% isppc -> computer
%
% Revision 1.33  2002/10/15 16:25:15  arno
% magnify edit boxes windows
%
% Revision 1.32  2002/10/15 14:42:15  arno
% button text aspect
%
% Revision 1.31  2002/08/20 22:33:34  arno
% debug for Mac
%
% Revision 1.30  2002/08/19 19:12:16  arno
% debugging last
%
% Revision 1.29  2002/08/19 19:10:13  arno
% figure bigger for MAC
%
% Revision 1.28  2002/08/17 02:38:42  arno
% debugging
%
% Revision 1.27  2002/08/14 18:17:23  arno
% new supergui arg
%
% Revision 1.26  2002/08/14 18:07:20  arno
% changing default checkbox backcolor
%
% Revision 1.25  2002/08/14 16:32:37  arno
% yfact takes into account button now
%
% Revision 1.24  2002/08/13 23:04:51  arno
% debug pop_merge
%
% Revision 1.23  2002/08/13 18:59:49  arno
% update automatic INSETY
%
% Revision 1.22  2002/08/13 18:56:01  arno
% adjustments
%
% Revision 1.21  2002/08/13 18:50:54  arno
% same
%
% Revision 1.20  2002/08/13 18:50:31  arno
% vert align
%
% Revision 1.19  2002/08/13 17:40:41  arno
% same
%
% Revision 1.18  2002/08/13 17:38:13  arno
% ordinate adjustment
%
% Revision 1.17  2002/08/13 17:28:56  arno
% new geometry
%
% Revision 1.16  2002/08/13 00:20:42  arno
% same
%
% Revision 1.15  2002/08/13 00:20:22  arno
% update position
%
% Revision 1.14  2002/08/12 21:42:59  arno
% ignore pushbutton extent
%
% Revision 1.13  2002/08/12 16:00:57  arno
% same
%
% Revision 1.12  2002/08/12 16:00:13  arno
% do not adapt size for edit windows
%
% Revision 1.11  2002/08/12 15:57:04  arno
% size calculation
%
% Revision 1.10  2002/08/12 14:50:15  arno
% color
%
% Revision 1.9  2002/08/12 14:47:40  arno
% color
%
% Revision 1.8  2002/08/12 14:30:55  arno
% background
%
% Revision 1.7  2002/08/12 01:17:42  arno
% update colors
%
% Revision 1.6  2002/08/12 00:41:41  arno
% updating default colors
%
% Revision 1.5  2002/07/18 17:18:31  arno
% offscreen correction
%
% Revision 1.4  2002/07/18 17:13:05  arno
% same
%
% Revision 1.3  2002/07/18 17:11:19  arno
% correct out-of screen problem
%
% Revision 1.2  2002/07/18 17:07:40  arno
% no modif
%
% Revision 1.1  2002/04/05 17:39:45  jorn
% Initial revision
%

function [handlers, outheight, allhandlers] = supergui( fig, geomx, geomy, varargin);

% handlers cell format
% allhandlers linear format

INSETX = 0.05; % x border absolute (5% of width)
INSETY = 0.05/length(geomx);  % y border relative (50% of heigth)  

if nargin < 2
	help supergui;
	return;
end;
if fig == 0
	figure('visible','off');
end;

% converting the geometry formats
% -------------------------------
if ~iscell( geomx )
	oldgeom = geomx;
	geomx = {};
	for row = 1:length(oldgeom)
		geomx = { geomx{:} ones(1, oldgeom(row)) };
	end;
end;
if isempty(geomy)
	geomy = ones(1, length(geomx));
end;

% setting relative width in percent
% ---------------------------------
for row = 1:length(geomx)
	tmprow = geomx{row};
	sumrow = sum(geomx{row});
	geomx{row} = 1.05*geomx{row}/sumrow;
	geomx{row} = geomx{row} - INSETX*(length(tmprow)-1)/length(tmprow);
end;

% setting relative height in percent
% ---------------------------------
sumcol = sum(geomy);
geomy  = (1.03+0.003*sumcol)*geomy/sumcol;
geomy  = geomy - INSETY*(length(geomy)-1)/length(geomy);

% get axis coordinates
% --------------------
set(gcf, 'menubar', 'none', 'numbertitle', 'off');		
pos = get(gca,'position'); % plot relative to current axes
q = [pos(1) pos(2) 0 0];
s = [pos(3) pos(4) pos(3) pos(4)]; % allow to use normalized position [0 100] for x and y
axis('off');

% creating guis
% -------------
counter = 1; % count the elements
outwidth = 0;
outheight = 0;
%height = 1.05/(length(geomx)+1)*(1-INSETY);
%posy = 1 - height - 1/length(geomx)*INSETY;
factmultx = 0;
factmulty = 0; %zeros(length(geomx));
posy = 0.98+(0.003*sumcol)/2+INSETY;
for row = 1:length(geomx)

	% init
    posx = -0.05;
	clear rowhandle;
	tmprow = geomx{row};
    height = geomy(row);
	posy = posy - height - INSETY;
	
	for column = 1:length(tmprow)

		width  = tmprow(column);
		try
			currentelem = varargin{ counter };
		catch
			fprintf('Warning: not all boxes were filled\n');
			return;
		end;		
		if ~isempty(currentelem)
			rowhandle(column) = uicontrol( 'unit', 'normalized', 'position', ...
						                      [posx posy width height].*s+q, currentelem{:});
						
			% this simply compute a factor so that all uicontrol will be visible
			% ------------------------------------------------------------------
			style = get( rowhandle(column), 'style');			
			set( rowhandle(column), 'units', 'pixels');			
			curpos = get(rowhandle(column), 'position');
			curext = get(rowhandle(column), 'extent');
			if ~strcmp(style, 'edit') & ~strcmp(style, 'pushbutton')
				factmultx = max(factmultx, curext(3)/curpos(3));
			end;
            factmulty = max(factmulty, curext(4)/curpos(4));
			set( rowhandle(column), 'units', 'normalized');			

			% Uniformize button text aspect
            % -----------------------------
            if strcmp(style, 'pushbutton')
                tmptext = lower(get(rowhandle(column), 'string'));
                try, tmptext(1) = upper(tmptext(1)); catch, end;
                set(rowhandle(column), 'string', tmptext);
            end;
        else 
			rowhandle(column) = 0;
		end;
		
		handlers{ row } = rowhandle;
		allhandlers(counter) = rowhandle(column);
		
		posx   = posx + width + INSETX;
		counter = counter+1;
	end;
	%posy      = posy - height - 1/length(geomx)*INSETY; %compensate for inset 
end;

% adjustments
% -----------
factmultx = factmultx+0.1; % because some text was still hidden
if factmultx < 0.3
	factmultx = 0.3;
end;

% for MAC (magnify figures that have edit fields)
% -------
warning off;
try, 
	if strcmp(computer, 'MAC')
		hh = findobj(allhandlers, 'style', 'edit');
		if ~isempty(hh)
			factmulty = factmulty*1.4;
		end;
	elseif ~isunix % windows
		hh = findobj(allhandlers, 'style', 'edit');
		if ~isempty(hh)
			factmulty = factmulty*1.15;
		end;
    end;
catch, end;
warning on;	

% scale and replace the figure in the screen
% -----------------------------------------
pos = get(gcf, 'position');
if factmulty > 1
	pos(2) = max(0,pos(2)+pos(4)-pos(4)*factmulty);
end;
pos(1) = pos(1)+pos(3)*(1-factmultx)/2;
pos(3) = pos(3)*factmultx;
pos(4) = pos(4)*factmulty;
set(gcf, 'position', pos);

% vertical alignment to bottom for text
% ---------------------------------------
for index = 1:length(allhandlers)
	if allhandlers(index) ~= 0
		if strcmp(get(allhandlers(index), 'style'), 'text')
			curpos = get(allhandlers(index), 'position');
			curext = get(allhandlers(index), 'extent');
			set(allhandlers(index), 'position',[curpos(1) curpos(2) curpos(3) curext(4)]);
		end;
	end;
end;

% setting defaults colors
%------------------------
try, icadefs;
catch,
	GUIBACKCOLOR        =  [.8 .8 .8];     
	GUIPOPBUTTONCOLOR   = [.8 .8 .8];    
	GUITEXTCOLOR        = [0 0 0];
end;

hh = findobj(allhandlers, 'parent', gcf, 'style', 'text');
%set(hh, 'BackgroundColor', get(gcf, 'color'), 'horizontalalignment', 'left');
set(hh, 'Backgroundcolor', GUIBACKCOLOR);
set(hh, 'foregroundcolor', GUITEXTCOLOR);
set(gcf, 'color',GUIBACKCOLOR );
set(hh, 'horizontalalignment', 'left');

hh = findobj(allhandlers, 'style', 'edit');
set(hh, 'BackgroundColor', [1 1 1]); %, 'horizontalalignment', 'right');

hh =findobj(allhandlers, 'parent', gcf, 'style', 'pushbutton');
set(hh, 'backgroundcolor', GUIPOPBUTTONCOLOR);
set(hh, 'foregroundcolor', GUITEXTCOLOR);
hh =findobj(allhandlers, 'parent', gcf, 'style', 'checkbox');
if isunix
	set(hh, 'backgroundcolor', GUIPOPBUTTONCOLOR);
	set(hh, 'foregroundcolor', GUITEXTCOLOR);	
else 
	set(hh, 'backgroundcolor', GUIBACKCOLOR);
	set(hh, 'foregroundcolor', GUITEXTCOLOR);
end;
hh =findobj(allhandlers, 'parent', gcf, 'style', 'listbox');
set(hh, 'backgroundcolor', GUIPOPBUTTONCOLOR);
set(hh, 'foregroundcolor', GUITEXTCOLOR);
hh =findobj(allhandlers, 'parent', gcf, 'style', 'radio');
set(hh, 'foregroundcolor', GUITEXTCOLOR);
set(hh, 'backgroundcolor', GUIPOPBUTTONCOLOR);

set(gcf, 'visible', 'on');

return;% finputcheck() - check matlab function { 'key', 'val' } inputs
%
% Usage: >> struct = finputcheck( varargin, fieldlist );
%        >> [struct varargin] = finputcheck( varargin, fieldlist, ... 
%                                                      callingfunc, mode );
%
% Input:
%   varargin  - varargin arguement from a function call with 'key', 'val'
%               arguements.
%   fieldlist - 3 or 4 columns cell array with one row per variable. The first
%               column contain the variable name, the second one the type, 
%                the third the accepted value range and the fourth one the 
%               defaultvalue. Ex:
%                  { 'varanme1' { 'value1' 'value2' } 'defaultvaluevar1' }
%                  { 'varanme2' { int1 int2 } 'defaultvaluevar2' }
%                  etc...
%               allowed types are 'boolean', 'integer', 'real', 'string', 
%               'cell' or 'struct'
%               the fifth column may contain the size (can be a cell array 
%               of size), but is optional.
%  callingfunc - calling function name for error messages. Default is none.
%  mode        - ['ignore'|'error'] ignore keywords that are not specified in
%                the fieldlist cell array or generate an error. Default is
%                'error'.
% Outputs:
%   struct     - checked structure
%   varargin   - residual varagin with non-recognized arguments
%
% Note: in case of error, a string is returned with the error message
%       instead of a structure.
%
% Example:
%	finputcheck(varargin,
%               { 'title'         'string'   []                       '';
%                 'percent'       'real'     [0 1]                     1 ;
%                 'elecamp'       'integer'  [1:10]                   [] });
%   'title' is a string with no default value
%   'percent' is a real number in between 0 and 1 and default value 1
%   'elecamp' is an integer that can tak value between 1 and 10
%
% Author: Arnaud Delorme, CNL / Salk Institute, 10 July 2002

%123456789012345678901234567890123456789012345678901234567890123456789012

% Copyright (C) Arnaud Delorme, CNL / Salk Institute, 10 July 2002, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% $Log: qsubfunc.m,v $
% Revision 1.31  2005/03/22 17:28:16  arno
% debugging some calls
%
% Revision 1.30  2003/04/15 22:20:17  arno
% adding path option
%
% Revision 1.29  2003/04/15 22:04:05  arno
% adding csh command
%
% Revision 1.28  2003/03/15 20:42:46  scott
% header edit -sm
%
% Revision 1.27  2002/11/15 01:42:24  scott
% Can not -> cannot
%
% Revision 1.26  2002/11/01 19:51:52  arno
% putting back error fle check
%
% Revision 1.25  2002/11/01 18:35:33  arno
% removing .e and .o file checking -> appear at once
%
% Revision 1.24  2002/11/01 18:22:28  arno
% disble file deletion
%
% Revision 1.23  2002/11/01 18:09:26  arno
% dealing with qsub error messages
%
% Revision 1.22  2002/11/01 17:04:16  arno
% force hostname
%
% Revision 1.21  2002/11/01 00:58:23  arno
% go back to the original directory for writing
%
% Revision 1.20  2002/10/31 22:42:26  arno
% back to previous dir
%
% Revision 1.19  2002/10/25 16:41:51  arno
% add quesdlg2 and more doc
%
% Revision 1.18  2002/10/25 01:59:33  arno
% adding finputcheck code
%
% Revision 1.11  2002/09/30 15:29:23  arno
% autorizing cell arrays for types
%
% Revision 1.10  2002/09/30 00:42:08  arno
% debug input arguments
%
% Revision 1.9  2002/07/29 18:00:53  arno
% debugging for NaN
%
% Revision 1.8  2002/07/29 17:24:22  arno
% header
%
% Revision 1.7  2002/07/20 19:10:41  arno
% debugging output
%
% Revision 1.6  2002/07/19 17:58:11  arno
% returning non-matched 'key' 'val' arguments
%
% Revision 1.5  2002/07/19 17:46:53  arno
% g empty if no varargin
%
% Revision 1.4  2002/07/19 16:27:14  arno
% adding ignore mode
%
% Revision 1.3  2002/07/10 02:18:32  arno
% header info
%
% Revision 1.2  2002/07/10 02:17:27  arno
% debugging error message passing
%
% Revision 1.1  2002/07/10 01:03:19  arno
% Initial revision
%

function [g, varargnew] = finputcheck( vararg, fieldlist, callfunc, mode )

	if nargin < 2
		help finputcheck;
		return;
	end;
	if nargin < 3
		callfunc = '';
	else 
		callfunc = [callfunc ' ' ];
	end;
    if nargin < 4
        mode = 'do not ignore';
    end;
	NAME = 1;
	TYPE = 2;
	VALS = 3;
	DEF  = 4;
	SIZE = 5;
	
	varargnew = {};
	% create structure
	% ----------------
	if ~isempty(vararg)
		for index=1:length(vararg)
			if iscell(vararg{index})
				vararg{index} = {vararg{index}};
			end;
		end;
		try
			g = struct(vararg{:});
		catch
			g = [ callfunc 'error: bad ''key'', ''val'' sequence' ]; return;
		end;
	else 
		g = [];
	end;
	
	for index = 1:size(fieldlist,NAME)
		% check if present
		% ----------------
		if ~isfield(g, fieldlist{index, NAME})
			g = setfield( g, fieldlist{index, NAME}, fieldlist{index, DEF});
		end;
		tmpval = getfield( g, {1}, fieldlist{index, NAME});
		
		% check type
		% ----------
        if ~iscell( fieldlist{index, TYPE} )
            res = fieldtest( fieldlist{index, NAME},  fieldlist{index, TYPE}, ...
                           fieldlist{index, VALS}, tmpval, callfunc );
            if isstr(res), g = res; return; end;
        else 
            testres = 0;
            tmplist = fieldlist;
            for it = 1:length( fieldlist{index, TYPE} )
                res{it} = fieldtest(  fieldlist{index, NAME},  fieldlist{index, TYPE}{it}, ...
                           fieldlist{index, VALS}, tmpval, callfunc );
                if ~isstr(res{it}), testres = 1; end;
            end;
            if testres == 0, g = strvcat(res{:}); return; end;
        end;
	end;
    
    % check if fields are defined
	% ---------------------------
	allfields = fieldnames(g);
	for index=1:length(allfields)
		if isempty(strmatch(allfields{index}, fieldlist(:, 1)'))
			if ~strcmpi(mode, 'ignore')
				g = [ callfunc 'error: undefined argument ''' allfields{index} '''']; return;
			end;
			varargnew{end+1} = allfields{index};
			varargnew{end+1} = getfield(g, {1}, allfields{index});
		end;
	end;


function g = fieldtest( fieldname, fieldtype, fieldval, tmpval, callfunc );
	NAME = 1;
	TYPE = 2;
	VALS = 3;
	DEF  = 4;
	SIZE = 5;
    g = [];
    
    switch fieldtype
     case { 'integer' 'real' 'boolean' }, 
      if ~isnumeric(tmpval)
          g = [ callfunc 'error: argument ''' fieldname ''' must be numeric' ]; return;
      end;
      if strcmp(fieldtype, 'boolean')
          if tmpval ~=0 & tmpval ~= 1
              g = [ callfunc 'error: argument ''' fieldname ''' must be 0 or 1' ]; return;
          end;  
      else 
          if strcmp(fieldtype, 'integer')
              if ~isempty(fieldval)
                  if (isnan(tmpval) & ~any(isnan(fieldval))) ...
                          & (~ismember(tmpval, fieldval))
                      g = [ callfunc 'error: wrong value for argument ''' fieldname '''' ]; return;
                  end;
              end;
          else % real
              if ~isempty(fieldval)
                  if tmpval < fieldval(1) | tmpval > fieldval(2)
                      g = [ callfunc 'error: value out of range for argument ''' fieldname '''' ]; return;
                  end;
              end;
          end;
      end;  
      
      
     case 'string'
      if ~isstr(tmpval)
          g = [ callfunc 'error: argument ''' fieldname ''' must be a string' ]; return;
      end;
      if ~isempty(fieldval)
          if isempty(strmatch(lower(tmpval), fieldval))
              g = [ callfunc 'error: wrong value for argument''' fieldname '''' ]; return;
          end;
      end;

      
     case 'cell'
      if ~iscell(tmpval)
          g = [ callfunc 'error: argument ''' fieldname ''' must be a cell array' ]; return;
      end;
      
      
     case 'struct'
      if ~isstruct(tmpval)
          g = [ callfunc 'error: argument ''' fieldname ''' must be a structure' ]; return;
      end;
      
      
     case '';
     otherwise, error([ 'finputcheck error: unrecognized type ''' fieldname '''' ]);
    end;
