function varargout = addremovelist(varargin)

%
% ADDREMOVELIST creates a GUI which contains two main listboxes, one on the left and one
% on the right. With the usage of two buttons (Add/Remove) the user can select items from
% the list on the left and transfer them to the list on the right and vice versa. Two
% checkboxes allow selecting multiple elements from each list and sort the list contents
% respectively. Hitting the OK button returns ADDREMOVELIST outputs (depending on user
% selections) in MATLAB's workspace. Hitting the Cancel button returns default outputs.
%
% Syntax:
% ---------------------------
%
% addremovelist
% [new_right_contents]=addremovelist
% [new_right_contents,new_left_contents]=addremovelist
% [new_right_contents,new_left_contents,right_index]=addremovelist
% [new_right_contents,new_left_contents,right_index]=addremovelist
% [new_right_contents,new_left_contents,right_index,left_index]=addremovelist
%
% also...
%
% addremovelist
% [new_right_contents]=addremovelist(data)
% [new_right_contents,new_left_contents]=addremovelist(data)
% [new_right_contents,new_left_contents,right_index]=addremovelist(data)
% [new_right_contents,new_left_contents,right_index]=addremovelist(data)
% [new_right_contents,new_left_contents,right_index,left_index]=addremovelist(data)
%
% and also...
%
% addremovelist('PropertyName',PropertyValue)
% [new_right_contents]=addremovelist('PropertyName',PropertyValue)
% [new_right_contents,new_left_contents]=addremovelist('PropertyName',PropertyValue)
% [new_right_contents,new_left_contents,right_index]=addremovelist('PropertyName',PropertyValue)
% [new_right_contents,new_left_contents,right_index]=addremovelist('PropertyName',PropertyValue)
% [new_right_contents,new_left_contents,right_index,left_index]=addremovelist('PropertyName',PropertyValue)
%
% In the above possible syntaxes, addremovelist without any further input arguments
% creates a usage example, addremove(data) creates the main window with data being the
% contents of the list on the left and all other properties to their default values and
% addremovelist('PropertyName',PropertyValue) starts the program by setting pairs of
% possible properties which are described below.
%
% PropertyName          PropertyValue                                                  
% -----------------------------------------------------------------------------
% LeftContents          A cell array of strings containing the contents of the list on the
%                       left (theoretically the list from which the user chooses a subpart
%                       of it as the output
%
% RightContents         A cell array of strings containing the contents of the list on the
%                       right (the output list or a list for an invert process - that is
%                       given the 'output' list create and 'input' list). Note that if
%                       this property is given the return of content corresponding
%                       incdices (see further below) will not be possible. If you try
%                       otherwise, an error will be thrown
%         
% ListNames             A cell array of strings of length 2 containing names for the lists.
%                       The first part of it corresponds to the name of the left list and
%                       the second part to the name of the right list. If not given the
%                       lists are named 'List 1' and 'List 2' respectively
%
% Title                 A string (NOT a cell array) with the window title.
%
% MultiSelect           It should be set to true or false (default true). It controls
%                       whether the user can make multiple selections from both lists or
%                       not. This property can also be changed from within the GUI.
%
% KeepSorted            It should be set to true or false (default false). It controls
%                       whether the list contents (and their corresponding index) remains
%                       sorted during the selections or not. This property can also be 
%                       changed from within the GUI.
%
% Output arguments:
% ---------------------------
% Name                  Value                                                  
% -----------------------------------------------------------------------------
% new_right_contents    A cell array of strings containing the new contents of the list on
%                       the right.
%
% new_left_contents     A cell array of strings containing the new contents of the list on
%                       the left.
%
% right_index           The indices of the items on the right list (derived from the list
%                       on the left)
%
% right_index           The indices of the items on the left list (derived from the list
%                       on the right)
%
% Concerning the indices (right and left): suppose that the initial content of the left
% list is the cell {'Alpha','Bravo','Charlie','Delta'}. Then the corresponding indices are
% [1 2 3 4]. If the user selects 'Bravo' and 'Delta' to be transfered to the list on the
% right, then the indices on the left list become [1 3] and on the right list [2 4]. If we
% choose to sort the contents of the lists then the indices are sorted respectively.
%
% Usage Examples
%----------------------------
%
% Example 1:
%
% [new_right_contents] = addremovelist
%
% new_right_contents = 
% 
%     'Mary'
%     'John'
%     'Susan'
%
% Example 2
%
% lcon = {'Apples','Pears','Oranges','Strawberries','Cherries'};
% lnames = {'Fruits','Buy'};
% wtitle= 'Grocer list';
% 
% [new_right_contents,new_left_contents] = addremovelist('LeftContents',lcon,...
%                                                        'ListNames',lnames,...
%                                                        'Title',wtitle,...
%                                                        'KeepSorted',true)
%
% new_right_contents = 
% 
%     'Oranges'
%     'Pears'
%     'Strawberries'
% 
% 
% new_left_contents = 
% 
%     'Apples'
%     'Cherries'
%
% Example 3
%
% lcon = {'Analysis 1','Analysis 2','Analysis 3','Analysis 4','Analysis 5'};
% wtitle= 'Analysis Results';
% 
% [new_right_contents,new_left_contents,right_index,left_index] = ...
%     addremovelist('LeftContents',lcon,...
%                   'Title',wtitle,...
%                   'KeepSorted',true)
%
% new_right_contents = 
% 
%     'Analysis 2'
%     'Analysis 4'
%     'Analysis 5'
% 
% 
% new_left_contents = 
% 
%     'Analysis 1'
%     'Analysis 3'
% 
% 
% right_index =
% 
%      2     4     5
% 
% 
% left_index =
% 
%      1     3
%
% Example 4
%
% lcon = {'Mary','George','John','Sarah','Susan','Joe','Eric','Helen'};
% lnames = {'Friends','Guests'};
% wtitle = 'Party';
%
% [new_right_contents,new_left_contents,right_index,left_index] = ...
%     addremovelist('LeftContents',lcon',...
%                   'ListNames',lnames,...
%                   'Title',wtitle,...
%                   'KeepSorted',true)
%
% new_right_contents = 
% 
%     'Eric'
%     'Helen'
%     'John'
%     'Mary'
% 
% 
% new_left_contents = 
% 
%     'George'
%     'Joe'
%     'Sarah'
%     'Susan'
% 
% 
% right_index =
% 
%      1     3     5     6
% 
% 
% left_index =
% 
%      2     4     7     8
%
% Example 5
%
% lcon = {'Mary','George','John','Sarah'};
% rcon = {'Susan','Joe','Eric','Helen'};
% lnames = {'Friends','Guests'};
% wtitle = 'Party';
% 
% [new_right_contents,new_left_contents] = ...
%     addremovelist('LeftContents',lcon',...
%                   'RightContents',rcon,...
%                   'ListNames',lnames,...
%                   'Title',wtitle,...
%                   'KeepSorted',true)
%
% new_right_contents = 
% 
%     'Eric'
%     'Helen'
%     'Joe'
%     'John'
%     'Sarah'
%     'Susan'
% 
% 
% new_left_contents = 
% 
%     'George'
%     'Mary'
%
% =============================================================================
%
% Author        : Panagiotis Moulos (pmoulos@eie.gr)
% Version       : 1.0
% First created : June 10, 2007
% Last modified : June 10, 2007
% 




% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @addremovelist_OpeningFcn, ...
                   'gui_OutputFcn',  @addremovelist_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before addremovelist is made visible.
function addremovelist_OpeningFcn(hObject, eventdata, handles, varargin)

% Set Window size and Position
set(handles.mainFigure,'Units','pixels')
screensize=get(0,'screensize');                       
winsize=get(handles.mainFigure,'Position');
winwidth=winsize(3);
winheight=winsize(4);
screenwidth=screensize(3);                           
screenheight=screensize(4);                           
winpos=[0.5*(screenwidth-winwidth),0.5*(screenheight-winheight),winwidth,winheight];                          
set(handles.mainFigure,'Position',winpos);

% Set all uicontrol units to normalized so as the default resize function works
set(handles.leftListName,'Units','normalized')
set(handles.rightListName,'Units','normalized')
set(handles.leftList,'Units','normalized')
set(handles.rightList,'Units','normalized')
set(handles.addElement,'Units','normalized')
set(handles.optionsPanel,'Units','normalized')
set(handles.removeElement,'Units','normalized')
set(handles.sortLists,'Units','normalized')
set(handles.allowMultiSelect,'Units','normalized')
set(handles.okButton,'Units','normalized')
set(handles.cancelButton,'Units','normalized')

% Initialize list contents by giving default values
handles.leftListContents='';
handles.rightListContents='';
handles.leftContents=handles.leftListContents;
handles.rightContents=handles.rightListContents;

% Define a flag for processing indices (if up position indices cannot be returned) and a
% flag to control whether the list contents are sorted or not and a multi-selection flag
handles.indflag=false;
handles.sort=false;
handles.multi=true;

% Get input variables from varargin and check for errors
if isempty(varargin) 
    % Create an example (initial contents always sorted)
    handles.leftListContents=sort({'Mary','George','John','Sarah','Susan'});
    % handles.leftListContents={'Analysis 1','Analysis 2','Analysis 3','Analysis 4','Analysis 5'};
    handles.rightListContents='';
    handles.leftContents=handles.leftListContents;
    handles.rightContents=handles.rightListContents;
    set(handles.leftList,'String',handles.leftListContents)
    set(handles.rightList,'String',handles.rightListContents)
    set(handles.leftListName,'String','Friends')
    set(handles.rightListName,'String','Guests')
    set(handles.mainFigure,'Name','Example of Add/Remove listboxes')
elseif length(varargin)==1
    if ~iscellstr(varargin{1})
        error('The first argument to %s must be a cell array of strings',mfilename)
    else
        % Initial contents always sorted
        handles.leftListContents=sort(varargin{1});
        handles.rightListContents='';
        handles.leftContents=handles.leftListContents;
        handles.rightContents='';
    end
elseif length(varargin)>1
    if rem(nargin,2)==0
        error('Incorrect number of arguments to %s.',mfilename);
    end
    okargs={'leftcontents','rightcontents','listnames','title','multiselect','keepsorted'};
    for i=1:2:length(varargin)-1
        parName=varargin{i};
        parVal=varargin{i+1};
        j=strmatch(lower(parName),okargs);
        if isempty(j)
            error('Unknown parameter name: %s.',parName);
        elseif length(j)>1
            error('Ambiguous parameter name: %s.',parName);
        else
            switch(j)
                case 1 % Left list contents
                    if ~iscellstr(parVal)
                        error('The %s parameter value must be a cell array of strings',parName)
                    else
                        % Initial contents always sorted
                        handles.leftListContents=sort(parVal);
                    end
                case 2 % Right list contents
                    if ~iscellstr(parVal) && ~isempty(parVal)
                        error('The %s parameter value must be a cell array of strings or empty',parName)
                    else
                         % Initial contents always sorted
                         handles.rightListContents=sort(parVal);
                    end
                case 3 % List names
                    if ~iscellstr(parVal) && ~length(parVal)==2
                        error('The %s parameter value must be a cell array of strings of length 2',parName)
                    else
                        set(handles.leftListName,'String',parVal{1})
                        set(handles.rightListName,'String',parVal{2})
                    end
                case 4 % Window title
                    if ~ischar(parVal)
                        error('The %s parameter value must be a string',parName)
                    else
                        set(handles.mainFigure,'Name',parVal)
                    end
                case 5 % Multiselection
                    if ~islogical(parVal)
                        error('The %s parameter value must be either true or false',parName)
                    else
                        if parVal
                            set(handles.allowMultiSelect,'Value',1)
                            handles.multi=true;
                        else
                            set(handles.allowMultiSelect,'Value',0)
                            handles.multi=false;
                        end
                    end
                case 6 % Keep contents and corresponding indices sorted
                    if ~islogical(parVal)
                        error('The %s parameter value must be either true or false',parName)
                    else
                        if parVal
                            set(handles.sortLists,'Value',1)
                            handles.sort=true;
                            handles.rightListContents=sort(handles.rightListContents);
                            handles.leftListContents=sort(handles.leftListContents);
                        else
                            set(handles.sortLists,'Value',0)
                            handles.sort=false;
                        end
                    end
                    
            end
        end
    end
end

% Initialize the listboxes (fill) and the indices of items
set(handles.leftList,'String',handles.leftListContents,'Value',1)
if handles.multi
    set(handles.leftList,'Max',length(handles.leftListContents))
end
if ~isempty(handles.rightListContents)
    set(handles.rightList,'String',handles.rightListContents,'Value',1)
    set(handles.removeElement,'Enable','on')
    if handles.multi
        set(handles.rightList,'Max',length(handles.rightListContents))
    end
    handles.indflag=true;
end
handles.leftIndices=1:length(handles.leftListContents);
handles.rightIndices=[];

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes addremovelist wait for user response (see UIRESUME)
uiwait(handles.mainFigure);


% --- Outputs from this function are returned to the command line.
function varargout = addremovelist_OutputFcn(hObject, eventdata, handles) 

% Handles button press
if (get(handles.cancelButton,'Value')==0)
    delete(handles.mainFigure);
elseif (get(handles.okButton,'Value')==0)
    delete(handles.mainFigure);
end

% Get default command line output from handles structure
varargout{1}=handles.rightContents;
varargout{2}=handles.leftContents;
if ~handles.indflag
    varargout{3}=handles.rightIndices;
    varargout{4}=handles.leftIndices;
end


%%%%%%%%%%%%%%%%%%% BEGIN LISTBOXES %%%%%%%%%%%%%%%%%%%%

% --- Executes on selection change in leftList.
function leftList_Callback(hObject, eventdata, handles)


% --- Executes during object creation, after setting all properties.
function leftList_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in rightList.
function rightList_Callback(hObject, eventdata, handles)


% --- Executes during object creation, after setting all properties.
function rightList_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%%%%%%%%%%%%%%%%%%% END LISTBOXES %%%%%%%%%%%%%%%%%%%%


%%%%%%%%%%%%%%%%%% BEGIN LISTBOX CONTROL BUTTONS %%%%%%%%%%%%%%%%%%%%

% --- Executes on button press in addElement.
function addElement_Callback(hObject, eventdata, handles)

% Determine what remains and what is to be added
leftNames=get(handles.leftList,'String');
leftToAddInd=get(handles.leftList,'Value');
leftToAddNames=leftNames(leftToAddInd);

% Do the job
oldstr=get(handles.rightList,'String');
addstr=leftToAddNames;
verify=ismember(addstr,oldstr);
if isempty(oldstr)
    newstr=[oldstr;addstr];
elseif length(verify)==1
    if ~ismember(addstr,oldstr)
        newstr=[oldstr;addstr];
    else
        newstr=oldstr;
    end
else
    newstr=[oldstr;addstr];
end

% Update the right list...
set(handles.rightList,'String',newstr)
% Sort it if wanted
if handles.sort
    neweststr=sort(newstr);
    set(handles.rightList,'String',neweststr)
end
% Handle multiple selection possibility
if handles.multi
    set(handles.rightList,'Max',length(newstr))
end

% Update the left list by removing elements
remaining=leftNames;
remaining(leftToAddInd)=[];
set(handles.leftList,'String',remaining,'Value',1)
% Sort it if wanted
if handles.sort
    [remainingnew,remainind]=sort(remaining);
    set(handles.leftList,'String',remainingnew,'Value',1)
end
% Handle multiple selection possibility
if handles.multi
    set(handles.leftList,'Max',length(remaining))
end

% Fix the final indices vectors
if ~handles.indflag
    if ~handles.sort
        newindices=handles.leftIndices(leftToAddInd);
        handles.leftIndices(leftToAddInd)=[];
        handles.rightIndices=[handles.rightIndices,newindices];
    else
        newindices=handles.leftIndices(leftToAddInd);
        handles.leftIndices(leftToAddInd)=[];
        handles.leftIndices=handles.leftIndices(remainind);
        handles.rightIndices=sort([handles.rightIndices,newindices]);
    end
end

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% % if size(handles.leftIndices,1)>size(handles.leftIndices,2)
% %     handles.leftIndices=handles.leftIndices';
% %     handles.rightIndices=handles.rightIndices';
% % end
% msgleft=['The left indices are : ',num2str(handles.leftIndices)];
% msgright=['The right indices are : ',num2str(handles.rightIndices)];
% disp(msgleft)
% disp(msgright)
% disp(' ')
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Manage Add/Remove buttons
if ~isempty(get(handles.rightList,'String'))
    set(handles.removeElement,'Enable','on')
end
if isempty(get(handles.leftList,'String'))
    set(handles.addElement,'Enable','off')
end

% Update the output strings
handles.leftContents=get(handles.leftList,'String');
handles.rightContents=get(handles.rightList,'String');
guidata(hObject,handles);


% --- Executes on button press in removeElement.
function removeElement_Callback(hObject, eventdata, handles)

% Determine what remains and what is to be added
rightNames=get(handles.rightList,'String');
rightToRemoveInd=get(handles.rightList,'Value');
rightToRemoveNames=rightNames(rightToRemoveInd);

% Do the job
oldstr=get(handles.leftList,'String');
addstr=rightToRemoveNames;
verify=ismember(addstr,oldstr);
if isempty(oldstr)
    newstr=[oldstr;addstr];
elseif length(verify)==1
    if ~ismember(addstr,oldstr)
        newstr=[oldstr;addstr];
    else
        newstr=oldstr;
    end
else
    newstr=[oldstr;addstr];
end

% Update the reft list...
set(handles.leftList,'String',newstr)
% Sort it if wanted
if handles.sort
    neweststr=sort(newstr);
    set(handles.leftList,'String',neweststr)
end
% Handle multiple selection possibility
if handles.multi
    set(handles.leftList,'Max',length(newstr))
end

% Update the right list by removing elements
remaining=rightNames;
remaining(rightToRemoveInd)=[];
set(handles.rightList,'String',remaining,'Value',1)
% Sort it if wanted
if handles.sort
    [remainingnew,remainind]=sort(remaining);
    set(handles.rightList,'String',remainingnew,'Value',1)
end
% Handle multiple selection possibility
if handles.multi
    set(handles.rightList,'Max',length(remaining))
end

% Fix the final indices vectors
if ~handles.indflag
    if ~handles.sort
        newindices=handles.rightIndices(rightToRemoveInd);
        handles.rightIndices(rightToRemoveInd)=[];
        handles.leftIndices=[handles.leftIndices,newindices];
    else
        newindices=handles.rightIndices(rightToRemoveInd);
        handles.rightIndices(rightToRemoveInd)=[];
        handles.rightIndices=handles.rightIndices(remainind);
        handles.leftIndices=sort([handles.leftIndices,newindices]);
    end
end

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% % if size(handles.leftIndices,1)>size(handles.leftIndices,2)
% %     handles.leftIndices=handles.leftIndices';
% %     handles.rightIndices=handles.rightIndices';
% % end
% msgleft=['The left indices are : ',num2str(handles.leftIndices)];
% msgright=['The right indices are : ',num2str(handles.rightIndices)];
% disp(msgleft)
% disp(msgright)
% disp(' ')
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Manage Add/Remove buttons
if ~isempty(get(handles.leftList,'String'))
    set(handles.addElement,'Enable','on')
end
if isempty(get(handles.rightList,'String'))
    set(handles.removeElement,'Enable','off')
end

% Update the output strings
handles.leftContents=get(handles.leftList,'String');
handles.rightContents=get(handles.rightList,'String');
guidata(hObject,handles);

%%%%%%%%%%%%%%%%%% BEGIN LISTBOX CONTROL BUTTONS %%%%%%%%%%%%%%%%%%%%


%%%%%%%%%%%%%%%%%% BEGIN OPTIONS PANEL %%%%%%%%%%%%%%%%%%%%

% --- Executes on button press in allowMultiSelect.
function allowMultiSelect_Callback(hObject, eventdata, handles)

if get(hObject,'Value')==1
    % Multiple selection allowed, maximum number of selections to length of contents
    handles.multi=true;
    set(handles.leftList,'Max',length(handles.leftContents))
else
    % Mutlple selection not allowed, current selection becomes the first item of possibly
    % a vector of selected items
    handles.multi=false;
    vals=get(handles.leftList,'Value');
    set(handles.leftList,'Value',vals(1),'Max',1)
end
guidata(hObject,handles);


% --- Executes on button press in sortLists.
function sortLists_Callback(hObject, eventdata, handles)

if get(hObject,'Value')==1
    handles.sort=true;
else
    handles.sort=false;
end
guidata(hObject,handles);

%%%%%%%%%%%%%%%%%% END OPTIONS PANEL %%%%%%%%%%%%%%%%%%%%


%%%%%%%%%%%%%%%%%% BEGIN CLOSE BUTTONS %%%%%%%%%%%%%%%%%%%%

% --- Executes on button press in okButton.
function okButton_Callback(hObject, eventdata, handles)

uiresume(handles.mainFigure);


% --- Executes on button press in cancelButton.
function cancelButton_Callback(hObject, eventdata, handles)

% Return default values
handles.leftContents=handles.leftListContents;
handles.rightContents=handles.rightListContents;
if ~handles.indflag
    handles.leftIndices=1:length(handles.leftListContents);
    handles.rightIndices=[];
end
guidata(hObject,handles);
uiresume(handles.mainFigure);

%%%%%%%%%%%%%%%%%% END CLOSE BUTTONS %%%%%%%%%%%%%%%%%%%%
