function out = CSEFlagDialog(items, varargin)
%CSEFLAGDIALOG makes a GUI dialog to select from options of diverse types 
% (checkboxes, radiobuttons, text-inputs and popup lists). Some options can 
% be linked, i.e. be mutually exclusive or be only enabled according to the 
% value of another option.
% It is an extension of CSEOptionDialog.
%
% out = CSEFlagDialog(items)
% out = CSEFlagDialog(items, title)
% out = CSEFlagDialog(items, title, msg)
% out = CSEFlagDialog(items, title, msg, options)
%
% items is an array structure containing the options, in the following fields:
% .name      is a string (the name of the option)
% .values    is a cell array of possible answer.
%             if empty, it is considered to contain "Yes" and "No", i.e.
%               the option is a checkbox or a radio-button.
%             if it contains only one numeric element, it is considered a
%               header, and not an active control.
%             if it contains only one text element, it is considered to be a
%               text input field, with the .values as default value.
%             Otherwise, it is a popup list with the choices given in values.
% .linked    [optional field] is an array of index in items indicating which
%            options are linked with the option. Linked options
%            will be grayed out when the option is set to "No".
%            If .linked contains negative elements, those will be grayed
%            out when the option is set to "Yes".
% .exclusive [optional field] is an array of index in items indicating which 
%            options are mutually exclusive with each option. When the option 
%            is set to "Yes", mutually exclusive options are set to "No". If 
%            the field does not exist, or is empty, the control if a checkbox
%            otherwise it is a radio-button.
%            Both .linked and .exclusive behaviour are only implemented for
%            "Yes"/"No" fields - but any field can be in "linked" and will
%            be grayed out.
% .default   [optional field] is an integer indicating the default value for 
%            each option (0: No, 1: Yes, -1: grayed out). Note that in the 
%            case of text input field, this field is irrelevant and the default 
%            is given in the "values" field; In the case of popup lists, the
%            default is an index in .values, or -1 (grayed out).
%            "Linked" and "Exclusive" are NOT evaluated in the initial layout
%            of the dialog, hence the default must be consequent with the rules,
%            e.g. linked fields of a "No" field must be grayed out.
% .indent    [optional field] allows an indentation (proportional to the value
%            of this field) from the position of the control. May be used
%            to graphically make "groups" as no "frames" are used.
% .help      [optional field] contains tooltips help texts. Can contain
%            "\n" to make multi-line help fields.
% title     is the window title (string)
% msg       is a string appearing on the top of the dialog
% options   is an optional structure containing options:
%           .center = 0|1 (center text msg)
%           .bold = 'light'|'normal'|'demi'|'bold'
%                   indicates how headers (see .values) must be printed out.
%           [more to come in future versions]
%
% The controls will be display in the order they appear in items.
%
% out contains an array of structure of answers:
%   out(i).answer = value of the control.
%   values are:
%       1 or 0 for "Yes"/"No" controls,
%       the index of the chosen item for list controls.
%       the text given for text inputs.
%   out is empty if cancel was chosen, negative integer if error.
%
% Written by L.Cavin, 07.12.2003, (c) CSE
% This code is free to use and modify for non-commercial purposes.
% Web address: http://ltcmail.ethz.ch/cavin/CSEDBLib.html#FLAGDIALOG
%
% =====================================================================
% An example of usage is: (in items, elements not mentionned are empty;
% e.g. items(1).linked is empty).
% 	items(1).name = 'Contact:';
% 	items(1).default = 0;
% 	items(1).values = {'email@address'};
% 	items(1).help = 'Enter your email address.';
% 	items(2).name = 'I will be coming!';
% 	items(2).default = 1;
% 	items(2).linked = [3 4 5 6];
% 	items(3).name = 'With my family';
% 	items(3).default = 1;
% 	items(3).exclusive = 4;
% 	items(3).indent = 1;
% 	items(4).name = 'Alone';
% 	items(4).default = 0;
% 	items(4).exclusive = 3;
% 	items(4).indent = 1;
% 	items(5).name = 'Transportation:';
% 	items(5).indent = 1;
% 	items(5).values = {1};
% 	items(6).name = 'Coming by';
% 	items(6).default = 1;
% 	items(6).indent = 2;
% 	items(6).values = {'Train'; 'Bus'; 'Foot'};
% 	items(6).help = 'Cars are polluting.\nUse public transportation whenever possible!';
% 	items(7).name = 'I''ll sure give a phone call!';
% 	items(7).default = 0;
% 
%   title = 'Birthday party incsription';
% 
%   msg = sprintf(['Dear friends,\nAs you all know, I am turning 30 next april.\nThis ' ...
%   'seems a worthy occasion to party a bit!\n\nWill you be able to ' ...
%   'attend?']);
% 
%   out = CSEFlagDialog(items, title, msg)


persistent handles;

if ischar(items)
    % this ain't no cell, so must be a callback
    callback_type = items;
    out = [];
elseif ~isa(items, 'struct')
    % this is an error
    warn('CSE:Dialog', 'The first parameter must be a stucture of items.');
    out = -1;
    return;
else
    % we must generate the dialog
    callback_type = 'create_dialog';
end

% first we do the callbacks, then we handle the initial call.
switch callback_type
    case 'item_click' % this is an action on a control
        ctrl_idx = get(gcbo, 'UserData');
        % should we "zero" other controls?
        if ~isempty(handles.items(ctrl_idx).exclusive) & get(gcbo, 'Value')==1
            for i = 1:length(handles.items(ctrl_idx).exclusive)
                set(handles.items_obj(handles.items(ctrl_idx).exclusive(i)), 'Value', 0);
            end
        end
        % should we disable or enable other elements
        if ~isempty(handles.items(ctrl_idx).linked)
            if get(gcbo, 'Value')==1
                % enable
                en_st = 'on';
                ds_st = 'off';
            else
                % disable
                en_st = 'off';
                ds_st = 'on';
            end
            for i = 1:length(handles.items(ctrl_idx).linked)
                tp = handles.items(ctrl_idx).linked(i);
                if tp > 0
                    set(handles.items_obj(tp), 'Enable', en_st);
                    if handles.head_obj(tp) > 0
                        set(handles.head_obj(tp), 'Enable', en_st);
                    end
                else
                    tp = -1 * tp;
                    set(handles.items_obj(tp), 'Enable', ds_st);
                    if handles.head_obj(tp) > 0
                        set(handles.head_obj(tp), 'Enable', ds_st);
                    end
                end
            end
        end
    case 'ok_click' % this is a click on the OK button
        for i = 1:length(handles.items_obj)
            if strcmp(get(handles.items_obj(i), 'Style'), 'edit')
                out(i).answer = cell2mat(get(handles.items_obj(i), 'String'));
            else
                out(i).answer = get(handles.items_obj(i), 'Value');
            end
        end
        set(gcbo,'userdata',out);
    case 'cancel_click' % click on cancel button
        out = [];
        set(handles.ok_button,'userdata',out);
    case 'create_dialog' % now this is actually the initial call
        options = [];
        msg = '';
        title = 'CSE Flag Dialog';
        if nargin > 1
            title = varargin{1};
            if nargin > 2
                msg = varargin{2};
                if nargin > 3
                    options = varargin{3};
                end
            end
        end
        handles.options = options;
        % precompute width and height of dialog:
        % width is 10 + the longest chain of characters + 10;
        % height is 1 + the number of lines in msg + 2 + number of items*2
        %           + 2 + 2 + 2 (sum: ... + 9)
        longest_chain = 0;
        item_length = 0;
        for i = 1:length(items)
            if ~isfield(items(i), 'indent') | isempty(items(i).indent)
                items(i).indent = 0;
            end
            if ~isfield(items(i), 'help')
                items(i).help = [];
            end
            if ~isfield(items(i), 'linked')
                items(i).linked = [];
            end
            if ~isfield(items(i), 'default')
                items(i).default = [];
            end
            if ~isfield(items(i), 'exclusive')
                items(i).exclusive = [];
            end
            val_length = 0;
            if length(items(i).values) > 0
                for j = 1:length(items(i).values)
                    if length(items(i).values{j}) > val_length
                        val_length = length(items(i).values{j});
                    end
                end
                if length(items(i).values) == 1
                    val_length = val_length + 10;
                end
            end
            items(i).mxlgt = val_length;
            if length(items(i).name)+val_length+items(i).indent*4 > item_length
                item_length = length(items(i).name)+val_length+items(i).indent*4;
            end
            if length(items(i).name)+val_length+items(i).indent*4 > longest_chain-10
                longest_chain = length(items(i).name)+10+val_length+items(i).indent*4;
            end
        end
        handles.items = items;
        a = [regexp(msg, '\n') length(msg)];
        for i = 2:length(a)
            if a(i)-a(i-1) > longest_chain
                longest_chain = a(i)-a(i-1);
            end
        end
        dial_width = max(longest_chain + 10, 50);
        dial_height = 9 + length(a)-1 + length(items)*2;
        if length(msg) == 0
            dial_height = dial_height -2;
        end
        item_length = (dial_width-item_length-10)/2;
        
        % A) create window: (invisible, for now)
        handles.dialog = dialog(    'Visible',          'off', ...
                                    'Units',            'characters', ...
                                    'Position',         [10 10 dial_width dial_height], ...
                                    'CloseRequestFcn',  'CSEFlagDialog(''cancel_click'');', ...
                                    'WindowStyle',      'normal', ...
                                    'Name',             title);
        % COMPATIBILITY TOWARDS R12: repeating instructions for safety:
        set(handles.dialog, 'Units', 'characters');
        set(handles.dialog, 'Position', [10 10 dial_width dial_height]);
                
        % B) create buttons:
        handles.ok_button = uicontrol(  'Units',    'characters', ...
                                        'Parent',   handles.dialog, ...
                                        'Position', [dial_width/2-20 2 10 2], ...
                                        'Callback', 'CSEFlagDialog(''ok_click'');', ...
                                        'String',   'OK' );
        handles.cancel_button = uicontrol(  'Units',    'characters', ...
                                            'Parent',   handles.dialog, ...
                                            'Position', [dial_width/2+10 2 10 2], ...
                                            'Callback', 'CSEFlagDialog(''cancel_click'');', ...
                                            'String',   'Cancel' );
        
        % C) create items:
        for i = 1:length(items)
            pos = length(items)-i+1;
            if isempty(items(i).values)
                ftype = 'radiobutton';
                if isempty(items(i).exclusive)
                    ftype = 'checkbox';
                end
                handles.head_obj(i) = -1;
                handles.items_obj(i) = uicontrol(   'Style',    ftype, ...
                                                    'Parent',   handles.dialog, ...
                                                    'Units',    'characters', ...
                                                    'Position', [items(i).indent*4+item_length 4+2*pos length(items(i).name)+10 1], ...
                                                    'Callback', 'CSEFlagDialog(''item_click'');', ...
                                                    'UserData', i, ...
                                                    'String',   items(i).name );
                if ~isempty(items(i).default) & items(i).default > 0
                    if strcmp(get(handles.items_obj(i), 'Style'), 'popupmenu')
                        if items(i).default > length(get(handles.items_obj(i), 'String'))
                            warn('CSE:Dialog', 'Initial value impossible for listbox %s.', items(i).name);
                            items(i).default = length(get(handles.items_obj(i), 'String'));
                        end
                    end
                    set(handles.items_obj(i), 'Value', items(i).default);
                end
            else
                if length(items(i).values) == 1
                    if isnumeric(items(i).values{1})
                        ftype = 'none';
                    else
                        ftype = 'edit';
                    end
                else
                    ftype = 'popupmenu';
                end
                % needs a "header":
                handles.head_obj(i) = uicontrol(    'Style',    'text', ...
                                                    'Parent',   handles.dialog, ...
                                                    'Units',    'characters', ...
                                                    'Position', [items(i).indent*4+item_length 4+2*pos (length(items(i).name)+2)*1.5 1], ...
                                                    'String',   items(i).name, ...
                                                    'HorizontalAlignment', 'left');
                if ~strcmp(ftype, 'none')
                    handles.items_obj(i) = uicontrol(   'Style',    ftype, ...
                                                        'Parent',   handles.dialog, ...
                                                        'BackgroundColor', 'white', ...
                                                        'Units',    'characters', ...
                                                        'Position', [items(i).indent*4+item_length+length(items(i).name)+3 4+2*pos-0.2 items(i).mxlgt+10 1.4], ...
                                                        'Callback', 'CSEFlagDialog(''item_click'');', ...
                                                        'UserData', i, ...
                                                        'String',   items(i).values, ...
                                                        'HorizontalAlignment', 'left' );
                    if ~isempty(items(i).default) & items(i).default > 0
                        if strcmp(get(handles.items_obj(i), 'Style'), 'popupmenu')
                            if items(i).default > length(get(handles.items_obj(i), 'String'))
                                warn('CSE:Dialog', 'Initial value impossible for listbox %s.', items(i).name);
                                items(i).default = length(get(handles.items_obj(i), 'String'));
                            end
                        end
                        set(handles.items_obj(i), 'Value', items(i).default);
                    end
                else
                    % was actually the "header" :-)
                    if isfield(handles.options, 'bold')
                        bld = handles.options.bold;
                    else
                        bld = 'normal';
                    end
                    set(handles.head_obj(i), 'FontWeight', bld);
                    handles.items_obj(i) = handles.head_obj(i);
                end
            end
            if ~isempty(items(i).default) & items(i).default < 0
                set(handles.items_obj(i), 'Enable', 'off');
            end
            if ~isempty(items(i).help)
                set(handles.items_obj(i), 'Tooltip', sprintf(items(i).help));
            end
        end
        
        % D) create message:
        if length(msg) > 0
            if isfield(handles.options, 'center') & handles.options.center == 1
                algn = 'center';
            else
                algn = 'left';
            end
            handles.text = uicontrol(  'Units',                 'characters', ...
                                       'HorizontalAlignment',   algn, ...
                                       'Style',                 'text', ...
                                       'Parent',                handles.dialog, ...
                                       'Position',              [5 dial_height-length(a)-1 longest_chain length(a)], ...
                                       'String',                msg );
        end
        
        % E) "center" and show dialog:
        movegui(handles.dialog, 'center');
        set(handles.dialog, 'Visible', 'on');
        
        % F) run dialog:
        waitfor(handles.ok_button,'userdata');
        
        % G) finish call:
        out = get(handles.ok_button,'userdata');
        delete(handles.dialog);
        clear handles;
        
    otherwise
        % this is a mistake
        warn('CSE:Dialog', 'Unknown Callback. Even if there is only one option, it must be passed as a cell.');
        out = -1;
end
        
        
%=====HELPER FUNCTION
function warn(tag, msg, varargin)
% patch for the warning function to make R13 calls compatible with R12

if str2num(version('-release')) < 13
    warning(sprintf(msg, varargin{:}));
else
    warning(tag, msg, varargin{:});
end
        