function [nph,nfh] = sbprogress(varargin)
%SBPROGRESS Creates a progress bar in the "status-bar section" of the figure
%[nph,nfh] = sbprogress(varargin)
%   sbprogress creates or updates a progress bar that is created at the
%   status-bar section of a figure. It can either be created on an existing
%   figure or raised along with a newly created figure. At least 2
%   parameters are
%   required (or zero for demo).
%
%   Syntax:
%       sbprogres
%       sbprogress(ph,x)
%       sbprogress([],x)
%       sbprogress(ph,'Close')
%       sbprogress(ph,'Redraw')
%       sbprogress(ph,x,'PropertyName',PropertyValue,...)
%       sbprogress(ph,'PropertyName',PropertyValue,...)
%       sbprogress([],x,'PropertyName',PropertyValue,...)
%       sbprogress([],'PropertyName',PropertyValue,...)
%       [nph,nfh] = sbprogress(...)
%
%   Description:
%       sbprogress runs a demo
%       sbprogress([],x) creates a NEW figure and a NEW progress bar on it with progress being set to x
%       sbprogress(ph,x) updates the progress bar at handles ph with progress being set/changed to x; this also redraws the progress bar
%       sbprogress(ph,'Close') closes an existing progress bar and also a figure if figure was created together with a progress bar otherwise only the progress bar will be removed from the figure
%       sbprogress(ph,'Redraw') redraws an existing progress bar; e.g., when figure is resized
%       sbprogress(...'PropertyName',PropertyValue,...) sets/resets some properties of the progress bar and the newly cretaed figure
%       [nph,nfh] = sbprogress(...) returns handles to newly created progress bar and figure if figure is created along with the progress bar
%
%   Properties:
%       'Parent':   a figure handle on which the progress is to be created
%                   (if empty or not given a new figure will also be raised)
%       'Msg':      a string message that is displayed on the left of a progress bar
%       'Color':    a color of the progress bar and the percentage text (defaults to blue)
%       'Visible':  'on'/'off'; shows/hides the progress bar and its bevel; if a figure is also created then this affect the figure too
%       'Title':    a string title that is displayed in the figure when the
%                   progress bar is created in a new figure; defaults to an empty string
%       'Size':     height of the progress bar; 1..12 (defaults to 1)
%       'Modal':    1 for newly created figure to be modal and 0 to be
%                   non-modal (default)
%       'DisplayMode': 1 (default) percentage and the remaining time are added
%                   to the title string (only when a new figure is raised);
%                   0 - no time info is displayed; 2 - the remaining time is
%                   added to the progress bar (shown only when x > 0);
%                   creating the sbprogress and/or setting the progress (x)
%                   to zero resets the timer
%
%   Notes:
%       X can be a value between -100..100; given x as a negative value
%       means that the percentage number will not be shown.
%       'Modal' and 'Title' are considered only in cases when a new figure is created alogn with the
%       progress bar.
%
%   EXAMPLES:   
%   sbprogress
%           Runs a demo
%   sbprogress([],10,'Msg','Progress:','Title','Test progress')
%           Creates a new figure with a progress bar...
%   ph = sbprogress([],-10,'Msg','Progress:','Title','Test progress')
%           Creates a new figure with a progress bar where the percentage
%           number is not shown; a handle array to the progress-bar axis is also returned
%   sbprogress(ph,'Redraw')
%           Redraws the ph progress bar
%   sbprogress(ph,20)
%           Re-sets the progress-bar's progress
%   ph = sbprogress([],20,'Msg','Test','Parent',fh)
%           Creates a progress bar on an existing figure whose handle is fh
%           and returns its handle array for further manipulation
%           (updating, changing,...)
%   set(fh,'UserData',ph,'ResizeFcn','sbprogress(get(gcf,''UserData''),''redraw'')')
%           Sets figure's (the one with the ph handle) resize function to
%           resize progress bar automatically
%
%   See also: STSBAR, ASCIIPROGRESS
%
%   Primoz Cermelj, 08.12.2003
%   (c) Primoz Cermelj, primoz.cermelj@email.si
%
%   Version: 1.1.0
%   Last revision: 19.02.2005
%--------------------------------------------------------------------------

%----------------
% SBPROGRESS history
%----------------
%
% [v.1.1.0] 19.02.2005
% - NEW: DisplayMode option added to display the remaining time
% - FIX: A bug relating to the Size property fixed
%
% [v.1.0.1] 05.09.2004
% - FIX: Minor update for error-checking
%
% [v.1.0.0] 28.05.2004
% - NEW: First official release

global W border

W = 400;        % Width of a newly created figure
border = 3;

% ---------------------
% First parameter-check and demo run
% ---------------------
if nargin == 0
    % Run a demo
    fh = figure;
    uicontrol('Style','text','String','Try to resize the figure...','Position',[150,200,200,25]);
    [nph,nfh] = sbprogress([],48.5,'Msg','Test:','Title','SBPROGRESS demo','Parent',fh);
    set(fh,'UserData',nph,'ResizeFcn','sbprogress(get(gcf,''UserData''),''redraw'')');
    [nph2,nfh2] = sbprogress([],-0.001,'Msg','Test 2:','Title','SBPROGRESS demo 2','Modal',0,'DisplayMode',1,'Color','r','Size',10);
    for ii=1:10
        sbprogress(nph2,-100*ii/10);
        pause(0.2);
    end
    return
elseif nargin < 2
    error('At least progress length should be given or ''Close'' or ''Redraw'' string');
end

ph = varargin{1};
x = varargin{2};
if ~ishandle(ph)
    error('ph is not a handle');
end

% Default properties
Props.Parent = [];
Props.Msg = '';
Props.Color = 'b';
Props.Title = '';
Props.Visible = 'on';
Props.DisplayMode = 1;
Props.Modal = 0;
Props.Size = 23;     % Default height of a newly created figure; this is aslo related to a progress bar height
propChanged = 0;     % if any property is being changed
phExists = ~isempty(ph);
fhExists = 1;
if phExists
    Data = get(ph,'UserData');
    if ~isfield(Data,'Props')
        error('Props field was not found in progress''s user data. Something was either removed or improper progress-bar handle was given.');
    else
        %%%%%%%%%%%%%%
        Props = Data.Props; % read props from current props
        %%%%%%%%%%%%%%
    end
end

% ---------------------
% Either 'Redraw' or 'Close' is assumed given in the second parameter x
% ---------------------
if ischar(x) & (strcmpi(x,'close') | strcmpi(x,'redraw'))
    if strcmpi(x,'close')
        Data = get(ph,'UserData');
        if ~isempty(Data) && isfield(Data,'fhCreated') && isfield(Data,'fh')
            if Data.fhCreated
                delete(Data.fh);
            else
                delete(ph);
            end
        else
            error('Wrong ph handle given');
        end        
    elseif strcmpi(x,'redraw')
        Data = get(ph,'UserData');
        if ~isempty(Data) && isfield(Data,'fhCreated') && isfield(Data,'fh')
            progress(ph,Data.x,Props,propChanged,Data.fh,Data.fhCreated,'update');
        else
            error('Wrong ph handle given');
        end        
    else
        error('Use sbprogress(ph,''redraw'') or sbprogress(ph,''close'')');
    end
    return
end
  
% ---------------------
% x might be ommited, empty, numeric value or a property name
% Sets any default values and any given ones given under properties
% ---------------------
if ischar(x)    % x is assumed being ommited (property is given)
    start = 2;
else
    start = 3;
end
if ~isnumeric(x) | isempty(x)
    if phExists
        x = Data.x;
    else
        x = 0;
    end
end
if nargin > 2       % additional properties are being passed
    nProps = nargin - start + 1;
    propChanged = 1;
    if ~logical( 2*floor(nProps/2)==nProps ), error('Additional properties must be given in pairs'), end;
    for ii=start:2:start+nProps-2
        if strcmpi(varargin{ii},'parent')
            Props.Parent = varargin{ii+1};
        elseif strcmpi(varargin{ii},'msg')
            Props.Msg = varargin{ii+1};
        elseif strcmpi(varargin{ii},'color')
            Props.Color = varargin{ii+1};            
        elseif strcmpi(varargin{ii},'title')
            Props.Title = varargin{ii+1};
        elseif strcmpi(varargin{ii},'visible')
            Props.Visible = varargin{ii+1};            
        elseif strcmpi(varargin{ii},'modal')
            Props.Modal = logical(varargin{ii+1});
        elseif strcmpi(varargin{ii},'size')
            Props.Size = varargin{ii+1};
        elseif strcmpi(varargin{ii},'displaymode')
            Props.DisplayMode = varargin{ii+1};
        else
            error('Unknown property given');
        end            
    end
    if (Props.Size < 1) | (Props.Size > 12)
        Props.Size = 23;
    else
        Props.Size = round(Props.Size) + 22;
    end
end

if isempty(Props.Parent) || ~ishandle(Props.Parent)
    fhExists = 0;
else
    if ~ishandle(ph)
        phExists = 0;
    end     
end
if ~phExists    % new progress bar is to be created
    fhCreated = 0;
    if ~fhExists
       nfh = createnewfig(Props); 
       fh = nfh;
       Props.Parent = fh;
       fhCreated = 1;
    else
       fh = Props.Parent;
    end
    nph = progress([],x,Props,1,fh,fhCreated,'new');
    ph = nph;
else
    progress(ph,x,Props,propChanged,Data.fh,Data.fhCreated,'update');
end

if nargout > 0
    nph = ph;
    if nargout > 1
        nfh = fh;
    end
end
%-------------------------------------


%-------------------------------------
function fh = createnewfig(Props)
global W
H = Props.Size;
if isempty(Props.Title); Props.Title = ''; end;
set(0,'Units','Pixels');
scr = get(0,'ScreenSize');
fh = figure('Visible','off');
set(fh,'Units','Pixels',...
       'Tag','progressBarFigure',...
       'Position',[scr(3)/2-W/2 scr(4)/2-H/2 W H],...
       'MenuBar','none',...
       'Resize','off',...
       'NumberTitle','off',...
       'Name',Props.Title);
if Props.Modal
    set(fh,'WindowStyle','modal');
end
if strcmpi(Props.Visible,'on')
    set(fh,'Visible','On');
end
%-------------------------------------

%-------------------------------------
function [ph,hMsg] = progress(ph,x,Props,propChanged,fh,fhCreated,state)
% Creates a new progress or updates an existing one
global border

H = Props.Size;
[fPos,pPos] = getcoords(fh,H);
w = pPos(3);
if x < -100; x = 0; end;
if x > 100; x = 100; end;
if propChanged
    if isempty(Props.Msg); Props.Msg = ''; end;
    if isempty(Props.Title); Props.Title = ''; end;
    if isempty(Props.Msg)
        Data.xMsgPos = 0;
    else
        Data.xMsgPos = 2*border;
    end
end
timeStr = '';
if nargin < 5 || strcmpi(state,'new')
    Data.startTime = clock;
    if Props.DisplayMode ~= 0
        timeStr = '?? remaining';
    end
    ph = axes;
    set(ph,'Tag','progressBarAxes','Units','Pixels','Position',pPos,'Box','off','Visible','off');      
    Data.hMsg = text(Data.xMsgPos,0.55*H,Props.Msg,'Units','Pixels','Tag','progressBarMsg','Visible','off');    
else            % update
    Data = get(ph,'UserData');
    if x == 0
        Data.startTime = clock;
    end
    if Props.DisplayMode ~= 0
        remTime = (100/abs(x+0.00000001)-1)*etime(clock,Data.startTime);        
        timeStr = sec2timestr(abs(remTime));
    end
    fh = Data.fh;
    set(ph,'Position',pPos);
    set(Data.hMsg,'String',Props.Msg,'Position',[Data.xMsgPos 0.55*H]);    
end
%-----
set(ph,'XLim',[0 w],'YLim',[0 H]);
%-----
tPos = get(Data.hMsg,'Extent');
if (tPos(3)+tPos(1)) > 0.5*w;  pStart = 0.5*w; else;  pStart = (tPos(3)+tPos(1)+2*border); end;
pEnd = pStart + abs(x)/100*abs(w-border-pStart);
%-----
if nargin < 5 || strcmpi(state,'new')    
    Data.hRec = rectangle('Position',[pStart border w-border-pStart-1 H-2*border-1],'Tag','progressBarRec','Visible','off');
    Data.hPatch = patch([pStart; pEnd; pEnd; pStart],[border; border; H-border-1; H-border-1],Props.Color,'Tag','progressBarPatch','Visible','off');
    if Props.DisplayMode == 2
        addStr = ['   ' timeStr];
    else
        addStr = '';
    end
    Data.hPercTxt = text( (pStart+w-border-1)/2,0.55*H,[num2str(abs(x),'%2.2f') '%' addStr],'Color',Props.Color,'Units','Pixels','FontWeight','bold','Tag','progressBarPercTxt','Visible','Off','EraseMode','xor','HorizontalAlignment','Center');
    Data.hFrame = panel(fh,ph,'new');
else                   
    set(Data.hRec,'Position',[pStart border w-border-pStart-1 H-2*border-1]);
    set(Data.hPatch,'XData',[pStart; pEnd; pEnd; pStart],'YData',[border; border; H-border-1; H-border-1],'FaceColor',Props.Color);
    if Props.DisplayMode == 2
        addStr = ['   ' timeStr];
    else
        addStr = '';
    end
    set(Data.hPercTxt,'Position',[(pStart+w-border-1)/2 0.55*H],'String',[num2str(abs(x),'%2.2f') '%' addStr],'Color',Props.Color);
    Data.hFrame = panel(fh,ph,'update');
end
%-----
if fhCreated
    fPos = get(fh,'Position');
    fPos(4) = H;
    if Props.DisplayMode == 1
        addStr = ['[' num2str(abs(x),'%2.2f') '%  ' timeStr ']'];
    else
        addStr = '';
    end
    set(fh,'Name',[Props.Title '  ' addStr],'Position',fPos);
    if propChanged
        if Props.Modal
            set(fh,'WindowStyle','modal');
        else
            set(fh,'WindowStyle','normal');
        end
        if strcmpi(Props.Visible,'on')
            set(fh,'Visible','on');
        else
            set(fh,'Visible','off');
        end
    end
end
%-----
if x >= 0 & strcmpi(Props.Visible,'on')
    set(Data.hPercTxt,'Visible','On');
else
    set(Data.hPercTxt,'Visible','Off');
end
if propChanged
    if strcmpi(Props.Visible,'on')
        set(Data.hRec,'Visible','On');
        set(Data.hMsg,'Visible','On');
        set(Data.hPatch,'Visible','On');
        set(Data.hFrame,'Visible','On');
    else
        set(Data.hRec,'Visible','Off');
        set(Data.hMsg,'Visible','Off');
        set(Data.hPatch,'Visible','Off');
        set(Data.hFrame,'Visible','Off');
    end
end
Data.Props = Props;
Data.pStart = pStart;
Data.fh = fh;
Data.fhCreated = fhCreated;
Data.x = x;
Data.msg = Props.Msg;
set(ph,'UserData',Data);
drawnow;
%-------------------------------------


%-------------------------------------
function frame = panel(fh,ph,state);
% Creates a virtual panel surrounding the progress bar.
% fh is the figure's handdle, ph is the progress' handle (axes).
% It returns a handle array designating the frame.
% See also the self-contained function BEVEL.
pos = get(ph,'Position');
x = pos(1);
y = pos(2);
w = pos(3);
h = pos(4);
col = rgb2hsv(get(fh,'Color'));
downColor = col;   downColor(2) = 0.5*downColor(2);  downColor(3) = 0.9; downColor = hsv2rgb(downColor);
upColor = col;    upColor(3) = 0.4;  upColor = hsv2rgb(upColor);
frame = zeros(4,1);
if nargin < 3 || strcmpi(state,'new')
    frame(1) = line([1 w],[h-1 h-1],[0 0],'Color',upColor,'Visible','off');
    frame(2) = line([1 1],[1 h-1],'Color',upColor,'Visible','off');
    frame(3) = line([2 w-1],[1 1],'Color',downColor,'Visible','off');
    frame(4) = line([w-1 w-1],[1 h-1],'Color',downColor,'Visible','off');
else
    Data = get(ph,'UserData');
    frame = Data.hFrame;
    set(frame(1),'XData',[1 w],'YData',[h-1 h-1]);
    set(frame(2),'XData',[1 1],'YData',[1 h-1]);
    set(frame(3),'XData',[2 w-1],'YData',[1 1]);
    set(frame(4),'XData',[w-1 w-1],'YData',[1 h-1]);
end
%-------------------------------------


%-------------------------------------
function [fPos,pPos] = getcoords(fh,H)
% Returns coordinates for axis, frame, and msg for our sbprogress (for
% creation or updating purposes). fPos is figure's position, pPos is
% progress-bar's position, framPos is frame's position
%
% Progress-bar axis
fUnits = get(fh,'Units');
set(fh,'Units','Pixels');
fPos = get(fh,'Position');
set(fh,'Units',fUnits);
pPos = fPos;
pPos(1) = 1;
pPos(2) = 2;
pPos(3) = pPos(3)-2;
pPos(4) = H-1;
%-------------------------------------


%-------------------------------------
function timeStr = sec2timestr(sec)
% Convert a time measurement from seconds into a human readable string.
dd = floor(sec/86400);
hh = floor((sec-dd*86400)/3600);
mm = floor((sec-dd*86400-hh*3600)/60);
ss = ceil(sec-dd*86400-hh*3600-mm*60);
timeStr = '';
if dd
    timeStr = sprintf('%d days, %d hr remaining',dd,hh);
elseif hh
    timeStr = sprintf('%d hr, %d min remaining',hh,mm);
elseif mm
    timeStr = sprintf('%d min, %d sec remaining',mm,ss);
else
    if sec < 0.01
        timeStr = 'completed';
    else
        timeStr = sprintf('%d seconds remaining',ss);
    end
end
%-------------------------------------