%DIPIMAGE   Starts the interactive user interface DIPimage.
%   DIPIMAGE, on its own, starts DIPimage, an interactive user interface
%   to do image processing.

% (C) Copyright 1999-2014               Pattern Recognition Group
%     All rights reserved               Faculty of Applied Physics
%                                       Delft University of Technology
%                                       Lorentzweg 1
%                                       2628 CJ Delft
%                                       The Netherlands
%
% Cris Luengo, April-July 2000.
% 9 August 2000:     Added support for DATASETS in the toolbox.
% 2 September 2000:  Corrected bug that didn't allow default vector 'array'.
% 19 September 2000: Added support for MEASUREMENT in the toolbox.
% 4 October 2000:    Corrected bug that fired warning when leaving a control empty.
% 16 November 2000:  Added support for multiple selection dialog box.
% 17 November 2000:  Created own version of multiple selection dialog box.
%                    Fixed bug that caused error when creating measurement control.
% 4 May 2001:        It is now possible to specify additional directories for functions
%                    to be added to the menu system.
%                    Improved error reporting when functions fail to be added to the menu.
% 5 May 2001:        Improved positioning of controls when there are no input or output
%                    variables.
%                    Added the 'handle' data type.
% 29 July 2001:      Fixed bug in the 'handle' data type.
%                    Error was generated when the 'Display' item was missing from the DEFS
%                    struct (while generating the dialog box, not the menu item). The name
%                    now is either "Display (command)" or just "command".
% 8 August 2001:     A function is not longer added to two menus, unless dictated by
%                    DIPMENUS. The menu name in DIPMENUS overrides the one asked for by
%                    the function itself.
%                    Added an exclude list to DIPMENUS.
% 15 August 2001:    Added the 'string' and 'boolean' data types.
% 16 August 2001:    Increased minimum width of the edit boxes.
% 23 August 2001:    Added DIPINIT to the list of functions never to add to the menus.
% 29 August 2001:    Changed MSGBOX for new PRIVATE/ERRORDIALOG.
% 15 September 2001: Preventing callbacks from adding or changing variables in the base
%                    workspace (where they appear to be executed!).
% 30 September 2001: Added RANGE_CHECK for 'handle'.
% 27 October 2001:   The button for the 'optionarray' type didn't move on window resize.
%                    Removed the function DIPHELP. Help goes on-line for the User Manual.
% 6 March 2002:      'option' and 'optionarray' now support a description list as well.
% 27 March 2002:     The DIPimage date and version number are taken from Contents.m
% 12 June 2002:      Aligning the data for the MULTISELECT dialog box.
% 13 August 2002:    Added TWAIN support under Windows.
% 15 November 2002:  Adapted for new style GETPARAMS.
% 24 November 2002:  Aligning the description field in the pull-down list for 'option'.
% 20 February 2004:  Updating contents of handle list through a context menu.
% 20 July 2004:      Added TYPE == 'measureid'.
% 23 July 2004:      Exchanged 'reload' popup menu in 'measureid' for callback function
%                    in 'measurement'
% 12 Oct 2004:       Added directory selection to gui (BR)
% 12 April 2005:     Fix for weird file names like .#bla.m created by emacs.
% 11 July 2005:      Added cellarray data type (BR & CL)
% 24 September 2005: Using the outparams.names as default names for output variables.
% 31 July 2007:      Updated email contact and user manual URL.
% 31 August 2007:    Not testing the input arguments in 'do_execute' any more.
%                    And a few small, inconsequential changes.
% 16 September 2007: Split out code from this function into PARAMTYPE and sub-functions.
% 18 September 2007: Removed the GETPARAMS('test') syntax with two output arguments.
%                    Split out remaining type-specific code into PARAMTYPE sub-functions.
% 19 September 2007: Using new function MATLABVERSION, not using TRUE.
% 10 April 2008:     Fixed problem that occured when CommandFilePath ends in '/'.
% 2 September 2008:  Fixed shadowed function reporting.
% 9 September 2008:  New!!! Improved!!! Now with Submenus!!!
% 10 September 2008: Tried fixing the access to the online help PDF file through FTP.
% 26 March 2009:     Fixed bug in routine that tests for shadowed functions (on Windows).
% 24 July 2009:      Added optional DEFS.OUTPARAMS.SUPPRESS value.
% 30 July 2009:      DO_CONTEXTMENU allows cell input with many data types.
% 18 December 2009:  Pressing ENTER with focus on a control in the GUI now executes the
%                    command (might not be smooth w/5.3). The PARAMTYPE_xxx functions no
%                    longer need to set the 'Tag' value for the controls.
% 21 April 2014:     Added macro recording feature. Figure's 'UserData' changed to a struct.
% 12 August 2014:    Avoiding missing 'isrecording' UserData after error during dialog creation.

function retval = dipimage(command,param)

Tag = 'DIPimage_Main_Window';
currsetting = get(0,'ShowHiddenHandles');
set(0,'ShowHiddenHandles','on');
fig = findobj('Tag',Tag);  % the DIPIMAGE figure handle
set(0,'ShowHiddenHandles',currsetting);

if nargin == 0
   % Do we already exist?
   if ~isempty(fig)
      % We exist: just raise the window.
      figure(fig)
      return
   end
   command = 'do_start';
else
   if isempty(fig)
      % We don't exist, but were called for some callback
      return
   end
end

switch lower(command)

% Initialize the DIPimage window
   case 'do_start'
      % Initialise diplib in case it is not done yet
      dip_initialise_libs('silent')
      % Create a new DIPimage window
      fig = figure( ...
         'BusyAction','cancel', ...
         'Color',get(0,'defaultuicontrolbackgroundcolor'), ...
         'CloseRequestFcn','delete(gcbf)', ...
         'CreateFcn','', ...
         'DeleteFcn','',...
         'HandleVisibility','off', ...
         'IntegerHandle','off', ...
         'Interruptible','off',...
         'MenuBar','none', ...
         'Name','DIPimage', ...
         'NumberTitle','off',...
         'ResizeFcn','dipimage do_resize', ...
         'Tag',Tag, ...
         'Units','pixels', ...
         'Visible','off',...
         'UserData',struct('command',[],'defs',[],'sizes',[],'isrecording',0));
      % Create the menu system
      try
         create_menus(fig);
      catch
         disp(firsterr);
         delete(fig);
         error('Couldn''t generate menu system.')
      end
      % Position the window
      currsetting = get(0,'Units');
      set(0,'Units','pixels');
      ss = get(0,'ScreenSize');
      set(0,'Units',currsetting);
      height = 200;
      width = floor(ss(3)/2);
      left = 10;                % guesstimate - will correct for later if MATLAB version >= 6.5
      bottom = ss(4)-height-60; % guesstimate - will correct for later if MATLAB version >= 6.5
      set(fig,'Position',[left,bottom,width,height]);
      set(fig,'Visible','on');
      % Put some data in the window to initialise it
      dipimage('do_about')
      % Now correct window position to make it fit snuggly agains the top left corner of the screen
      border = dipfig_getbordersize(fig);
      fp = get(fig,'Position');
      fp(1) = border(1);
      fp(2) = ss(4)-fp(4)-border(2)-border(4);
      set(fig,'Position',fp);
      % Do user-customized initialization
      %#function dipinit
      if exist('dipinit','file')
         evalin('base','dipinit');
      end

% Reposition the controls when the window is resized
   case 'do_resize'
      format_dialog(fig);

% Start or stop recording the macro
   case 'do_macro'
      h = gcbo;
      ud = get(fig,'UserData');
      if ud.isrecording
         % stop recording the macro
         ud.isrecording = 0;
         ud.macroname = '';
         set(gcbo,'Label','Record macro')
      else
         % start recording the macro
         [fname,pname] = uiputfile('macro.m','Record macro as');
         if isequal(fname,0) | isequal(pname,0)
            % user pressed cancel
            return;
         end
         ud.macroname = fullfile(pname,fname);
         try
            f = fopen(ud.macroname,'at');
            fprintf(f,'%% --- Macro recording started %s ---\n',datestr(now));
            fclose(f);
            edit(ud.macroname);
         catch
            errordialog('Error writing to selected file.')
            return
         end
         set(gcbo,'Label','Stop recording macro')
         ud.isrecording = 1;
      end
      set(fig,'UserData',ud);

% Handle the EXIT command
   case 'do_exit'
      answ = questiondialog('Are you sure you want to exit DIPimage?');
      if strcmpi(answ,'Yes')
         delete(fig);
      end

% Handle the RELOAD MENUS command
   case 'do_reload'
      % Create the menu system
      try
         create_menus(fig);
      catch
         disp(firsterr);
         delete(fig);
         error('Couldn''t generate menu system.')
      end

% Handle the ABOUT commands
   case 'do_about'
      h = gcbo;
      if ~isempty(h)
         topic = get(h,'Label');
      else
         topic = '';
      end
      switch topic
         case 'About DIPlib'
            d = dip_getlibraryinformation;
            strings = {[d.name,': ',d.description],...
                       ['Version ',d.version,' (',d.date,', ',d.type,')'],...
                       ['Contact: ',d.supervisor,' (mailto:',d.contact,')'],...
                       ['Authors: ',d.authors]};
         case 'About dipIO'
            d = dipio_getlibraryinformation;
            strings = {[d.name,': ',d.description],...
                       ['Version ',d.version,' (',d.date,', ',d.type,')'],...
                       ['Contact: ',d.supervisor,' (mailto:',d.contact,')'],...
                       ['Authors: ',d.authors]};
         otherwise
            d = get_version_number;
            if isempty(d.version)
               strings = {d.name,...
                          ['Development version'],...
                          ['Contact: ',d.supervisor,' (mailto:',d.contact,')'],...
                          ['Authors: ',d.authors]};
            else
               strings = {d.name,...
                          ['Version ',d.version,' (',d.date,')'],...
                          ['Contact: ',d.supervisor,' (mailto:',d.contact,')'],...
                          ['Authors: ',d.authors]};
            end
      end
      create_dialog(fig,'about','About DIPimage',strings)

% Handle the HELP button and commands
   case 'do_help'
      h = gcbo;
      if ~isempty(h)
         tag = get(h,'Tag');
         switch tag
            case 'help'
               ud = get(fig,'UserData');
               helpwin(ud.command)
               return
            case 'reference'
               helpwin('dipimage')
               return
         end
      end
      helpfile = dipgetpref('UserManualLocation');
      try
         web(helpfile,'-browser');
      catch
         try
            web(helpfile);
         catch
            error(['Could not open the online help file. You can find it here:',10,helpfile]);
         end
      end

% Handle the BROWSE buttons
   case 'do_browse'
      if nargin~=2
         return
      end
      if strcmp(param.direction,'open')
         tmp = getfile(param.mask,param.title);
      elseif strcmp(param.direction,'save')
         tmp = putfile(param.mask,param.title);
      elseif strcmp(param.direction,'dir')
         tmp = getdir(param.title);
      end
      if ~isempty(tmp)
         set(param.editbox,'String',tmp);
      end

% Handle the SELECT buttons
   case 'do_select'
      if nargin~=2
         return
      end
      % Create a list of possible values and a list for display
      displist = get(gcbo,'UserData');
      displist = displist.options;
      if isstruct(displist)
         items = {displist.name};
      else
         items = displist;
      end
      % Find currently selected items
      try
         c = eval(get(param.editbox,'String'));
      catch
         c = {};
      end
      if isempty(c)
         selection = 1;
      else
         if iscell(c)
            N = length(c);
            selection = zeros(N,1);
            for ii=1:N
               if ischar(c{ii})
                  selection(ii) = find(strcmp(items,c{ii}));
               else
                  selection(ii) = find(strcmp(items,mat2str(c{ii})));
               end
            end
         elseif isnumeric(c)
            selection = find([items{:}]==c);
         elseif ischar(c)
            selection = find(strcmp(items,c));
         else
            selection = 1;
         end
      end
      % Display dialog box
      [selection,button] = multiselect(param.title,displist,selection);
      % If user pressed OK, button==1 else button==0
      if button
         % Put new selection in edit box;
         set(param.editbox,'String',cell2str(items(selection)));
      end

% Create and display a context menu for a control
   case 'do_contextmenu'
      if nargin~=2
         return
      end
      if ischar(param)
         param = {param};
      end
      if ~iscellstr(param)
         return % Shouldn't happen unless people make a weird paramtype_XXX function. Warn?
      end
      %#function whos
      list = evalin('base','whos');
      I = [];
      for ii=1:length(param)
         I = [I,find(strcmp({list.class},param{ii}))];
      end
      list = {list(unique(I)).name};
      if ~any(cellfun('isempty',list))
         cm = get(gcbo,'UserData');
         cm = cm.contextmenu;
         delete(get(cm,'Children'));
         for ii=1:length(list)
            %#function eval set get gcbo
            uimenu(cm,'Label',list{ii},'Callback',['set(get(get(gcbo,''Parent''),''UserData''),''String'',''',...
                   list{ii},''');eval(get(get(get(gcbo,''Parent''),''UserData''),''Callback''))']);
         end
         set(cm,'Visible','on');
      end
% Context menu for 'handle' control: update the handle list
   case 'do_reloadcontrol'
      if nargin~=2
         return
      end
      switch param.type
         case 'handle'
            [figh,strings] = handlelist(param.selection);
            if isempty(figh)
               strings = {''};
               figh = 0;
            end
            set(param.popupmenu,'String',strings,'Value',1,'UserData',struct('figh',figh));
         case 'measureid'
            h = gcbo;
            % Sometimes the callback object is a menu in the measurement data edit box
            if strcmp(get(h,'type'),'uimenu')
               h = get(get(h,'Parent'),'UserData');
            end
            meas = get(h,'String');
            try
               meas = evalin('base',meas);
            catch
               errordialog({['Error evaluating parameter # ',num2str(param.num),':'],firsterr(1)})
               return
            end
            if exist('meas','var') & isa(meas,'dip_measurement')
               strings = fieldnames(meas);
            else
               strings = {''};
            end
            set(param.popupmenu,'String',strings,'Value',1);
         otherwise
            return
      end
% Callback for the EXECUTE button
   case 'do_execute'
      ud = get(fig,'UserData');
      if ~isempty(ud)
         execute_dialog(fig,ud);
      end

% Handle the menu commands
   otherwise
      % Find command parameters
      try
         d = eval([command,'(''DIP_GetParamList'')']);
      catch
         % This should mean that the function has changed or was deleted
         % (maybe by a change of the PATH variable).
         errordialog({'Error retrieving ParamList structure:',firsterr})
         return
      end
      if isempty(d) | ~isstruct(d)
         errordialog('No ParamList structure')
      else
         msg = getparams('test',d);
         if ~isempty(msg)
            errordialog({'ParamList structure incorrect:',msg})
            return
         end
         create_dialog(fig,'command',command,d);
      end

end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                              Creating the menus                           %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


function create_menus(fig)

% Start by removing all the already defined menus
delete(findobj(fig,'Tag','mainmenu'));

if isunix
   use_lower = 0;
else
   use_lower = 1;
end

% read in the default menus and command ordering, and the exclude list
try
   [menulist,excludelist] = dipmenus('DIP_GetMenus');
catch
   disp([13,13,'Warning: Call to DIPMENUS failed:',13,13,firsterr,13,13])
   menulist = {'File I/O',{'reload','exit'}}; % Bare minimum?
   excludelist = {};
end
excludelist = unique([excludelist,{'contents','readme','dipimage','dipmenus','localdipmenus','dipinit'}]);

% read in all the commands in the command directories that support being menued
p = {fileparts(which('dipimage'))};
p = [p,convertpath(dipgetpref('CommandFilePath'))];
if use_lower
   p = lower(p);
end
files = {};
for ii=1:length(p)
   f = what(p{ii});
   files = [files;f.m];
end
files = unique(files);
list = {};
debug_mode = dipgetpref('DebugMode');
for ii=1:length(files)
   command = files{ii}(1:end-2); % remove '.m'
   if ~any(strcmp(command,excludelist))
      if isletter(command(1)) & all(isletter(command) | command== '_' | ('0' <= command & command <= '9'))
         full_name = which(command);
         full_name_dir = fileparts(full_name);
         if use_lower
            full_name_dir = lower(full_name_dir);
         end
         if isempty(full_name)
            disp(['Warning: ''',command,''' not being added to the menu.'])
            disp('    Reason: it''s not on MATLAB''s path.')
            disp(' ')
         elseif ~any(strcmp(full_name_dir,p))
            disp(['Warning: ''',command,''' not being added to the menu.'])
            disp('    Reason: it''s shadowed by:')
            disp({full_name})
         else
            if debug_mode
               full_name = which('-all',command);
               % FULL_NAME contains more than one entry if there is a shadowed
               % function somewhere. If there are more than one item, it's possible
               % that it's because there's a P-file or a MEX-file in the same directory.
               % This is OK. It's also possible that there's some private function
               % listed. that's OK as well. Finally, overloaded methods can be listed
               % also. This is OK as well.
               % We remove these overloaded and private functions first.
               toremove = [];
               for jj=1:length(full_name)
                  [fn_path,fn_name,fn_ext] = fileparts(full_name{jj});
                  if ~isempty(fn_path)
                  % we check for empty directory name here, because WHICH can return 'split is a Java method',
                  % which yields an emtpy directory name.
                     if use_lower
                        fn_path = lower(fn_path);
                     end
                     if strcmp(fn_path,full_name_dir)
                        % they're in the same directory!
                        toremove(end+1) = jj;
                     else
                        [fn_path,fn_path] = fileparts(fn_path);
                        if fn_path(1) == '@' | strcmp(fn_path,'private')
                           % it's an overload or private function!
                           toremove(end+1) = jj;
                        end
                     end
                  end
               end
               full_name(toremove) = [];
               if ~isempty(full_name)
                  disp(['Warning: ''',command,''' is shadowing some other function:'])
                  disp(full_name)
               end
            end
            try
               d = eval([command,'(''DIP_GetParamList'')']);
               if isfield(d,'menu')
                  menu = d(1).menu;
                  if ~strcmp(menu,'none')
                     if isfield(d,'display')
                        list = [list;{menu,d(1).display,command}];
                     else
                        list = [list;{menu,'',command}];
                     end
                  end
               else
                  if debug_mode
                     disp(['Warning: ''',command,''' not being added to the menu.'])
                     disp('    Reason: the ParamList structure does not contain a ''menu'' field.')
                     disp(' ')
                  end
               end
            catch
               if debug_mode
                  disp(['Warning: ''',command,''' not being added to the menu.'])
                  disp('    Reason: it generated the error:')
                  disp(lasterr)
                  disp(' ')
               end
            end
         end
      end
   end
end

% these functions are already placed in the list
predef_cmds = unique(cat(2,menulist{:,2}));
I = strcmp(predef_cmds,'-');
predef_cmds(I) = [];
I = strmatch('#',predef_cmds);
predef_cmds(I) = [];
% add '-' to the end of each menu
for ii=1:size(menulist,1)
   if ~isempty(menulist{ii,2}) & ~strcmpi(menulist{ii,2}{end},'-')
      menulist{ii,2} = [menulist{ii,2},{'-'}];
   end
end
% add the items in LIST if they aren't in PREDEF_CMDS
for ii=1:size(list,1)
   if ~any(strcmp(predef_cmds,list{ii,3}))
      I = find(strcmp(menulist(:,1),list{ii,1}));
      if isempty(I)
         menulist = [menulist;{list{ii,1},{}}];
         I = size(menulist,1);
      else
         I = I(1); % There shouldn't be multiple items here, but...
      end
      menulist{I,2} = [menulist{I,2},list(ii,3)];
   end
end

% iterate through the MENULIST structure to get all the menus made
menulist{1,3} = [];
for ii=1:size(menulist,1)
   menulist{ii,3} = uimenu(fig,'Label',menulist{ii,1},'Tag','mainmenu');
end
% iterate through the MENULIST structure to get all the items made
for ii=1:size(menulist,1)
   h = menulist{ii,3};
   menuitems = menulist{ii,2};
   separator = 'off';
   for jj=1:length(menuitems)
      if menuitems{jj}(1) == '#'
         % we put the indicated menu as a submenu here
         mn = menuitems{jj}(2:end);
         I = find(strcmp(mn,menulist(:,1)));
         if ~isempty(I)
            I = I(1); % There cannot be more items here, but just in case.
            set(menulist{I,3},'parent',h,'Separator',separator);
            separator = 'off';
         end
      else
         switch menuitems{jj}
            case '-'
               % add separator above next menu item
               separator = 'on';
            case 'macro'
               uimenu(h,'Label','Record macro','Callback','dipimage do_macro','Separator',separator);
               separator = 'off';
            case 'reload'
               uimenu(h,'Label','Reload menus','Callback','dipimage do_reload','Separator',separator);
               separator = 'off';
            case 'exit'
               uimenu(h,'Label','Exit','Callback','dipimage do_exit','Separator',separator);
               separator = 'off';
            case 'twainselect'
               uimenu(h,'Label','Select source...','Callback','acquireim(''SelectSource'')','Separator',separator);
               separator = 'off';
            otherwise
               I = find(strcmp(list(:,3),menuitems{jj}));
               if ~isempty(I)
                  I = I(1); % There cannot be more items here, but just in case.
                  if ~isempty(list{I,2})
                     uimenu(h,'Label',[list{I,2},' (',list{I,3},')'],'Callback',...
                           ['dipimage ',list{I,3}],'Separator',separator);
                  else
                     uimenu(h,'Label',list{I,3},'Callback',...
                           ['dipimage ',list{I,3}],'Separator',separator);
                  end
                  separator = 'off';
               end
         end
      end
   end
end
% add the help menu at the end
h = uimenu(fig,'Label','Help','Separator','on','Tag','mainmenu');
uimenu(h,'Label','DIPimage User Manual','Callback','dipimage do_help');
uimenu(h,'Label','DIPimage Reference','Callback','dipimage do_help','tag','reference');
uimenu(h,'Label','About DIPimage','Callback','dipimage do_about','Separator','on');
uimenu(h,'Label','About DIPlib','Callback','dipimage do_about');
uimenu(h,'Label','About dipIO','Callback','dipimage do_about');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                           Creating the dialog box                         %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


function create_dialog(fig,type,command,defs)
% Remove all controls
delete(findobj(fig,'Type','uicontrol'));
delete(findobj(fig,'Type','uicontextmenu'));
ud = get(fig,'UserData');
set(fig,'UserData',[]);
if matlabver_ge([6,0])
   % Avoid the dialog being executed when manipulating a pull-down menu.
   % Alas, in MATLAB 5 we cannot modify the 'CurrentCharacter' value.
   set(fig,'CurrentCharacter',' ');
end
% Get sizes and positions
sizes.leftmargin = 20;          % Space between left edge and controls
sizes.internalmargin = 10;      % Horizontal space between controls
sizes.rightmargin = 20;         % Space between right edge and controls
sizes.topmargin = 10;           % Space between top edge and controls
sizes.bottommargin = 10;        % Space between bottom edge and controls
sizes.framemargin = 10;         % Space between frame and controls
sizes.vspacing = 6;             % Space between rows
sizes.vslack = 4;               % Extra space allotted to controls (around text)
sizes.hslack = 10;              % Extra space allotted to controls (around text)
h = uicontrol(fig,'Style','text','String','Browse...','Visible','off');
extent = get(h,'Extent'); % Size for browse button
delete(h);
sizes.mincntrlwidth = 4*(extent(3)+sizes.hslack)+sizes.internalmargin;
sizes.textheight = extent(4);
sizes.controlheight = sizes.textheight + sizes.vslack;
sizes.lineheight = sizes.controlheight + sizes.vspacing;
% There are two different types of dialogs
switch lower(type)
   case 'about'
      % This is the about dialog box
      nnames = length(defs);
      sizes.labelwidth = max(get_maximum_width(fig,defs,0),get_maximum_width(fig,defs{1},1));
      sizes.totalwidth = sizes.labelwidth;
      pos = set_dialog_size(fig,sizes,nnames+1);
      sizes.leftmargin = floor((pos(3)-sizes.labelwidth)/2);
      add_control(fig,pos,sizes,0,'title',defs{1})
      for ii=2:nnames
         add_control(fig,pos,sizes,ii-1,'text',defs{ii})
      end
      command = type;
   case 'command'
      % This is the command execution dialog box
      if isfield(defs,'display')
         title = [defs.display,' (',command,')'];
      else
         title = command;
      end
      if isfield(defs,'inparams')
         nin = length(defs.inparams);
         names = {defs.inparams.description};
      else
         nin = 0;
         names = {};
      end
      if isfield(defs,'outparams')
         nout = length(defs.outparams);
         names = {names{:},defs.outparams.description};
      else
         nout = 0;
      end
      nnames = nin+nout;
      sizes.labelwidth = get_maximum_width(fig,names,0);
      % We want the controls to be at least sizes.mincntrlwidth.
      sizes.totalwidth = sizes.labelwidth+sizes.mincntrlwidth+sizes.internalmargin;
      sizes.totalwidth = max(get_maximum_width(fig,title,1),sizes.totalwidth);
      pos = set_dialog_size(fig,sizes,nnames+3+(nin*nout>0));
      if pos(3) > (sizes.totalwidth+sizes.leftmargin+sizes.rightmargin)
         % Adjust sizes to center stuff in dialog.
         sizes.leftmargin = floor((pos(3)-sizes.totalwidth)/2);
         sizes.rightmargin = pos(3)-sizes.totalwidth-sizes.leftmargin;
      end
      add_frames(fig,pos,sizes,nin,nout)
      add_control(fig,pos,sizes,0,'title',title)
      for ii=1:nin
         add_control(fig,pos,sizes,ii,'input',defs.inparams(ii))
      end
      for ii=1:nout
         add_control(fig,pos,sizes,nin+(nin>0)+ii,'output',defs.outparams(ii))
      end
      add_buttons(fig,pos,sizes,nnames+2+(nin*nout>0))
   otherwise
      error('Internal error: unknown dialog mode.')
end
% Make all controls visible
set(findobj(fig,'Type','uicontrol'),'Visible','on');
ud.command = command;
ud.defs = defs;
ud.sizes = sizes;
if ~isfield(ud,'isrecording')
   ud.isrecording = 0;
end
set(fig,'UserData',ud);


%
% A subroutine to reposition the dialog box controls
%    Callback to DO_RESIZE
%
function format_dialog(fig)
% Get sizes and positions
ud = get(fig,'UserData');
if ~isempty(ud) && ~isempty(ud.command)
   command = ud.command;
   defs = ud.defs;
   sizes = ud.sizes;
   sizes.leftmargin = 20;
   sizes.rightmargin = 20;    % these values need resetting.
   if strcmp(command,'about')
      % It is an about dialog box.
      nnames = length(defs);
      pos = set_dialog_size(fig,sizes,nnames+1);
      sizes.leftmargin = floor((pos(3)-sizes.labelwidth)/2);
      for ii=0:nnames-1
         h = findobj(fig,'Type','uicontrol','Tag',['label',num2str(ii)]);
         position_text_control(h,pos,sizes,ii)
      end
   else
      % It is a command dialog box.
      if isfield(defs,'inparams')
         nin = length(defs.inparams);
      else
         nin = 0;
      end
      if isfield(defs,'outparams')
         nout = length(defs.outparams);
      else
         nout = 0;
      end
      nnames = nin+nout;
      pos = set_dialog_size(fig,sizes,nnames+3+(nin*nout>0));
      if pos(3) > (sizes.totalwidth+sizes.leftmargin+sizes.rightmargin)
         % Adjust sizes to center stuff in dialog.
         sizes.leftmargin = floor((pos(3)-sizes.totalwidth)/2);
         sizes.rightmargin = pos(3)-sizes.totalwidth-sizes.leftmargin;
      end
      h1 = findobj(fig,'Type','uicontrol','Tag','inframe');
      h2 = findobj(fig,'Type','uicontrol','Tag','outframe');
      position_frames(h1,h2,pos,sizes,nin,nout)
      h = findobj(fig,'Type','uicontrol','Tag','label0');
      position_text_control(h,pos,sizes,0)
      for ii=1:nin
         h = findobj(fig,'Type','uicontrol','Tag',['label',num2str(ii)]);
         position_label(h,pos,sizes,ii)
         eh = findobj(fig,'Type','uicontrol','Tag',['control',num2str(ii)]);
         bh = findobj(fig,'Type','uicontrol','Tag',['control',num2str(ii),'bis']);
         if isempty(bh)
            position_control(eh,pos,sizes,ii)
         else
            position_browse_control(eh,bh,pos,sizes,ii)
         end
         h = get(eh,'UserData');
         if isfield(h,'contextmenu')
            h = h.contextmenu;
            p = get(eh,'Position');
            set(h,'Position',p(1:2));
         end
      end
      for ii=1:nout
         h = findobj(fig,'Type','uicontrol','Tag',['label',num2str(nin+(nin>0)+ii)]);
         position_label(h,pos,sizes,nin+(nin>0)+ii)
         h = findobj(fig,'Type','uicontrol','Tag',['control',num2str(nin+(nin>0)+ii)]);
         position_control(h,pos,sizes,nin+(nin>0)+ii)
      end
      he = findobj(fig,'Type','uicontrol','Tag','execute');
      hh = findobj(fig,'Type','uicontrol','Tag','help');
      position_buttons(he,hh,pos,sizes,nnames+2+(nin*nout>0));
   end
% else DON'T try to do anything if there is no data in dialog (error condition?)
end


%
% A subroutine to add the two frames to the input dialog.
%
function add_frames(fig,pos,sizes,nin,nout)
% fig = figure handle
% pos = figure position
% sizes = sizes of margins and controls
% nin,nout = number of lines in the two boxes.
if nin > 0
   h1 = uicontrol(fig,...
                  'Style','frame',...
                  'Visible','off',...
                  'Tag','inframe');
else
   h1 = [];
end
if nout > 0
   h2 = uicontrol(fig,...
                  'Style','frame',...
                  'Visible','off',...
                  'Tag','outframe');
else
   h2 = [];
end
position_frames(h1,h2,pos,sizes,nin,nout)


%
% A subroutine to add a control to the dialog box.
%
function add_control(fig,pos,sizes,index,style,data)
% fig = figure handle
% pos = figure position
% sizes = sizes of margins and controls
% index = item number in the dialog box (1 is topmost control, 0 is topmost label)
% style = {'title','text','input','output'}
% data = string for title or text, param struct for input or output
switch style
   case 'title' % Create a simple title control (same as text, but uses bold font.)
      labh = uicontrol(fig,...
                       'Style','text',...
                       'String',data,...
                       'Visible','off',...
                       'Tag',['label',num2str(index)],...
                       'HorizontalAlignment','center',...
                       'FontWeight','bold');
      position_text_control(labh,pos,sizes,index)
   case 'text' % Create a simple text control
      labh = uicontrol(fig,...
                       'Style','text',...
                       'String',data,...
                       'Visible','off',...
                       'Tag',['label',num2str(index)],...
                       'HorizontalAlignment','left');
      position_text_control(labh,pos,sizes,index)
   case 'output' % Create a variable name entering control for output variables
      labh = uicontrol(fig,...
                       'Style','text',...
                       'String',data.description,...
                       'Visible','off',...
                       'Tag',['label',num2str(index)],...
                       'HorizontalAlignment','right');
      position_label(labh,pos,sizes,index)
      h = uicontrol(fig,...
                    'Style','edit',...
                    'String',data.name,...
                    'Visible','off',...
                    'Tag',['control',num2str(index)],...
                    'HorizontalAlignment','left',...
                    'BackgroundColor',[1,1,1]);
      position_control(h,pos,sizes,index)
      set(h,'Callback','if double(get(gcbf,''CurrentCharacter''))==13, dipimage do_execute, end')
   case 'input' % Create a control for input variables through PARAMTYPE
      labh = uicontrol(fig,...
                       'Style','text',...
                       'String',data.description,...
                       'Visible','off',...
                       'Tag',['label',num2str(index)],...
                       'HorizontalAlignment','right');
      position_label(labh,pos,sizes,index)
      h = paramtype('control_create',data,fig);
      eh = h(1);
      set(eh,'Callback','if double(get(gcbf,''CurrentCharacter''))==13, dipimage do_execute, end')
      set(eh,'Tag',['control',num2str(index)]);
      if length(h)==1
         position_control(eh,pos,sizes,index)
      else
         bh = h(2);
         set(bh,'Tag',['control',num2str(index),'bis']);
         position_browse_control(eh,bh,pos,sizes,index)
      end
      h = get(eh,'UserData');
      if isfield(h,'contextmenu')
         h = h.contextmenu;
         p = get(eh,'Position');
         set(h,'Position',p(1:2));
      end
end


%
% A subroutine to add a button to the dialog box.
%
function add_buttons(fig,pos,sizes,index)
% fig = figure handle
% pos = figure position
% sizes = sizes of margins and controls
% index = item number in the dialog box (1 is topmost control)
he = uicontrol(fig,...
               'Style','pushbutton',...
               'String','Execute',...
               'Visible','off',...
               'Tag','execute',...
               'HorizontalAlignment','center',...
               'BusyAction','cancel',...
               'Interruptible','off',...
               'Callback','dipimage do_execute');
hh = uicontrol(fig,...
               'Style','pushbutton',...
               'String','Help',...
               'Visible','off',...
               'Tag','help',...
               'HorizontalAlignment','center',...
               'BusyAction','cancel',...
               'Interruptible','off',...
               'Callback','dipimage do_help');
position_buttons(he,hh,pos,sizes,index);


%
% A subroutine to set the height of the dialog box
%
function pos = set_dialog_size(fig,sizes,nlines)
pos = get(fig,'Position');
newheight = sizes.lineheight*nlines+sizes.bottommargin+sizes.topmargin;
pos(2) = pos(2)+pos(4)-newheight;
pos(4) = newheight;
pos(3) = max(pos(3),sizes.totalwidth+sizes.leftmargin+sizes.rightmargin);
set(fig,'Position',pos);


%
% A subroutine to measure the width of the strings in NAMES
%
function width = get_maximum_width(fig,names,usebold)
h = uicontrol(fig,'Style','text','String',names,'Visible','off');
if usebold
   set(h,'FontWeight','bold');
end
width = get(h,'Extent');
delete(h);
width = width(3);


%
% A bunch of subroutines to set the position of the different types of controls
%
function position_label(h,pos,sizes,index)
if ~isempty(h)
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(index+1)+floor(sizes.vslack/2);
   set(h,'Position',[sizes.leftmargin,bottom,sizes.labelwidth,sizes.textheight]);
end

function position_text_control(h,pos,sizes,index)
if ~isempty(h)
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(index+1)+floor(sizes.vslack/2);
   width = get(h,'Extent');
   set(h,'Position',[sizes.leftmargin,bottom,width(3),sizes.textheight]);
end

function position_control(h,pos,sizes,index)
if ~isempty(h)
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(index+1);
   width = pos(3)-sizes.leftmargin-sizes.labelwidth-sizes.internalmargin-sizes.rightmargin;
   left = sizes.leftmargin+sizes.labelwidth+sizes.internalmargin;
   set(h,'Position',[left,bottom,width,sizes.controlheight]);
end

function position_browse_control(eh,bh,pos,sizes,index)
if ~isempty(eh) & ~isempty(bh)
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(index+1);
   width = pos(3)-sizes.leftmargin-sizes.labelwidth-sizes.internalmargin-sizes.rightmargin;
   left = sizes.leftmargin+sizes.labelwidth+sizes.internalmargin;
   w = get(bh,'Extent');
   w = w(3)+sizes.hslack;
   l = pos(3)-sizes.rightmargin-w;
   set(bh,'Position',[l,bottom,w,sizes.controlheight]);
   set(eh,'Position',[left,bottom,width-w-sizes.internalmargin,sizes.controlheight]);
end

function position_buttons(he,hh,pos,sizes,index)
left = pos(3)-sizes.rightmargin;
bottom = pos(4)-sizes.topmargin-sizes.lineheight*(index+1);
if ~isempty(he)
   width = get(he,'Extent');
   width = width(3)+sizes.hslack;
   left = left-width;
   set(he,'Position',[left,bottom,width,sizes.controlheight]);
   left = left-sizes.internalmargin;
end
if ~isempty(hh)
   width = get(hh,'Extent');
   width = width(3)+sizes.hslack;
   left = left-width;
   set(hh,'Position',[left,bottom,width,sizes.controlheight]);
end

function position_frames(h1,h2,pos,sizes,nin,nout)
left = sizes.leftmargin-sizes.framemargin;
width = sizes.totalwidth+2*sizes.framemargin;
offset = floor(sizes.controlheight/2)+sizes.vspacing;
if ~isempty(h1)
   height = (nin+1)*sizes.lineheight+1;
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(nin+1)-offset;
   set(h1,'Position',[left,bottom,width,height]);
end
if ~isempty(h2)
   height = (nout+1)*sizes.lineheight+1;
   bottom = pos(4)-sizes.topmargin-sizes.lineheight*(nout+1+nin+(nin>0))-offset;
   set(h2,'Position',[left,bottom,width,height]);
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                           Executing the commands                          %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


function execute_dialog(fig,ud)
if matlabver_ge([6,0])
   % Avoid the dialog being executed when manipulating a pull-down menu.
   % Alas, in MATLAB 5 we cannot modify the 'CurrentCharacter' value.
   set(fig,'CurrentCharacter',' ');
end
defs = ud.defs;
if isfield(defs,'inparams')
   nin = length(defs.inparams);
else
   nin = 0;
end
if isfield(defs,'outparams')
   nout = length(defs.outparams);
else
   nout = 0;
end
if isfield(defs,'output_select')
   selectout = defs.output_select;
else
   selectout = 0;
end
nnames = nin+nout;
instrs = cell(1,nin);
inparams = cell(1,nin);
outparams = cell(1,nout);
dispout = ones(1,nout);
% Get & check all input parameters
for ii=1:nin
   control = findobj(fig,'Type','uicontrol','Tag',['control',num2str(ii)]);
   try
      [inparams{ii},instrs{ii}] = paramtype('control_value',defs.inparams(ii),control);
   catch
      errordialog({['Error evaluating parameter #',num2str(ii),':'],firsterr(1)})
      return
   end
end
% Get all output parameters
if selectout
   selected = zeros(1,nout);
   for ii=1:nout
      control = findobj(fig,'Type','uicontrol','Tag',...
                ['control',num2str(nin+(nin>0)+ii)]);
      outparams{ii} = get(control,'String');
      if ~isempty(outparams{ii})
         if ~is_valid_varname(outparams{ii})
            errordialog(['Illegal name for output variable #',num2str(ii)])
            return
         else
            selected(ii) = 1;
         end
      end
      if isfield(defs.outparams,'suppress')
         dispout(ii) = ~defs.outparams(ii).suppress;
      end
   end
   if sum(selected)<1
      errordialog('Need at least one output variable.')
      return
   end
   names = {defs.outparams.name};
   nin = nin+1;
   selected = find(selected);
   inparams{nin} = names(selected);
   instrs{nin} = cell2str(inparams{nin});
   outparams = outparams(selected);
   dispout = dispout(selected);
   nout = length(outparams);
else
   emptyname = 0;
   for ii=1:nout
      control = findobj(fig,'Type','uicontrol','Tag',...
                ['control',num2str(nin+(nin>0)+ii)]);
      outparams{ii} = get(control,'String');
      if isempty(outparams{ii})
         if ~emptyname
            emptyname = ii;
            if ii==1
               errordialog('First output variable needs a name.')
               return
            end
         end
      elseif ~is_valid_varname(outparams{ii})
         errordialog(['Illegal name for output variable #',num2str(ii)])
         return
      elseif emptyname
         errordialog('Cannot assign value into variable without assigning earlier values.')
         return
      end
      if isfield(defs.outparams,'suppress')
         dispout(ii) = ~defs.outparams(ii).suppress;
      end
   end
   if emptyname>0
      nout = emptyname-1;
   end
end
% Print to command line
if dipgetpref('PutInCommandWindow') | ud.isrecording
   if nout > 1
      str = ['[',outparams{1}];
      for ii=2:nout
         str = [str,',',outparams{ii}];
      end
      str = [str,'] = ',ud.command];
   elseif nout == 1 & ~strcmp(outparams{1},'ans')
      str = [outparams{1},' = ',ud.command];
   else
      str = ud.command;
   end
   if nin > 1
      str = [str,'(',instrs{1}];
      for ii=2:nin
         str = [str,',',instrs{ii}];
      end
      str = [str,')'];
   elseif nin == 1
      str = [str,'(',instrs{1},')'];
   end
   if dipgetpref('PutInCommandWindow')
      disp(str);
   end
   if ud.isrecording
      try
         f = fopen(ud.macroname,'at');
         fprintf(f,'%s\n',str);
         fclose(f);
      catch
         errordialog('Error writing to macro file.')
         return
      end
   end
end
% Evaluate command
try
   if nout > 0
      outvalues = cell(1,nout);
      [outvalues{:}] = feval(ud.command,inparams{:});
   else
      feval(ud.command,inparams{:});
   end
catch
   errordialog({'Error executing the command:',firsterr(1)})
   %disp(lasterr)
   return
end
% Assign results in base workspace
for ii=1:nout
   try
      assignin('base',outparams{ii},outvalues{ii});
      if dispout(ii)
         evalin('base',outparams{ii});
      end
   catch
      errordialog({['Error putting output argument # ',num2str(ii),':'],firsterr(1)})
   end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                    Obtaing the DIPimage version number                    %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


function d = get_version_number
d.name = 'DIPimage toolbox for MATLAB';
d.supervisor = 'Lucas J. van Vliet';
d.authors = 'Cris Luengo, Bernd Rieger, Michael van Ginkel & Lucas van Vliet';
d.contact = 'info@diplib.org';
d.version = [];
d.date = 'unknown';
p = fileparts(which('dipimage'));
f = fopen(fullfile(p,'Contents.m'),'r');
if f>0
   % The file has the following first two lines:
   %    % DIPlib, a scientific image processing library
   %    % Version #.#.#   dd-MMM-YYYY
   % This is a very fixed style, but we will make it a bit
   % robust just in case.
   ln = fgetl(f);
   ln = fgetl(f);
   fclose(f);
   I = findstr(upper(ln),'VERSION');
   if ~isempty(I)
      I = I(1);
      ln = ln (I+7:end);
      I = ln==' ';
      I1 = find(~I);
      if ~isempty(I1)
         I1 = I1(1);
         I2 = find(I(I1:end));
         if ~isempty(I2)
            I2 = I2(1);
         else
            I2 = length(I)-I1+2;
         end
         I2 = I2(1);
         d.version = ln(I1+(0:I2-2));
         ln = ln((I1+I2-1):end);
         I = ln==' ';
         I1 = find(~I);
         if ~isempty(I1)
            I1 = I1(1);
            I2 = find(I(I1:end));
            if ~isempty(I2)
               I2 = I2(1);
            else
               I2 = length(I)-I1+2;
            end
            d.date = ln(I1+(0:I2-2));
         end
         return
      end
   end
end

