function [varargout] = progressbar(action, varargin)
%PROGRESSBAR Display, modify, and/or update a progress indicator
% PROGRESSBAR is a comprehensive progress indicator for long or
% repetitive calculations. It allows the user to display any
% title or message, the start time, the elapsed time, and an
% estimate of the time remaining, as well as progress - all
% while comsuming minimal execution time.
%
% PROGRESSBAR is a multi-function fuction, where the first
% argument is an action string specifying what function to
% carry out:
%
% H = PROGRESSBAR is the same as H = PROGRESSBAR('create').
%
% H = PROGRESSBAR('create', ['TITLE'], ['MESSAGE']) creates
% and initializes the progress indicator. The figure name is
% given 'TITLE', and 'MESSAGE' is placed in the text field
% above the bar. The progress bar is placed at 0, the
% refresh rate is set to .1 seconds, and the stop signal is
% set to 0. In addition, the status string, containing start
% time, elapsed time, estimated time left, and percent complete
% are all initialized (NOTE: the elapsed time is set to
% '??:??:??' since no estimate can be made yet).
%
% For the remaining functions (except internal ones), the
% figure handle H must be the second arguement passed, right
% after the action string.
%
% PROGRESSBAR('cancelbutton', H, ['CALLBACK']) creates a cancel
% button that changes the stop signal (defined as the STATUS
% field '.stop' - see below) to 1, when the cancel button is
% pressed, as well as executing 'CALLBACK' if it is specified
% (NOTE: pushing the cancel button does not prevent further
% execution of calls to PROGRESSBAR, it simply sets the stop
% signal to 1. You can check on the status of the stop signal
% with the action string 'status' - see definition below).
%
% PROGRESSBAR('update', H, X) first checks to see if the last
% update was over the refresh rate seconds ago. If it was,
% then the bar is set to X, where X is the fractional
% completion (NOTE: X is forced to the interval [0, 1]). In
% addition, the status string is updated to reflect changes in
% the elapsed and estimated times, as well as the percent
% complete.
%
% PROGRESSBAR('message', H, 'MESSAGE') replaces the current
% message text (the text field above the bar) with 'MESSAGE'
% (NOTE: there is enough room for three to four lines of text
% in the message field on most screen resolutions - use
% SPRINTF with '\n' for newline).
%
% PROGRESSBAR('title', H, 'TITLE') replaces the current title
% text (the figure name) with 'TITLE' (NOTE: unlike the
% message field, there is only room for one line of text).
%
% PROGRESSBAR('refresh', H, NEWRATE) replaces the current
% refresh rate (in seconds) with NEWRATE. This value controls
% how quickly the meter and status strings can be updated.
% Setting a low value (such as .0001) provides very detailed
% progress information, but can slow the execution loop because
% too much time is spent updating PROGRESSBAR. Setting a high
% value (such as 5) minimizes PROGRESSBAR's execution time, but
% may be too corse a measure of progress. The default value of
% .1 seems to be a fairly optimal balance, but this function is
% provided so that the rate can be arbitrarily set.
%
% PROGRESSBAR('kill', H) closes the progress indicator. All
% status information is lost (NOTE: If you want this
% information before killing the indicator, use the action
% string 'status' - see below).
%
% STATUS = PROGRESSBAR('status', H) returns a structure
% containing all information pertinent to the progress
% indicator:
% .winscale = the scale factor used to create all UI
% objects so that they remain fairly consistant across
% different screen resolutions.
%
% .X = the fractional completion (between [0, 1])
%
% .stop = stop signal (becomes 1 when cancel button is
% pressed)
%
% .refresh = refresh rate (in seconds)
%
% .h = UI graphics handles:
% .pbar = progress indicator figure handle (H)
% .message = message text handle
% .status = status string text handle
% .axes = bar axes handle
% .line = bar line handle
% [.cancelbutton] = cancel button handle (if called)
%
% .time = date vectors for status string (NOTE: some
% date vectors have 0's for the yr/mo/day spots - this
% function doesn't care about them, meaning that these
% times become inaccurate if the calculations take longer
% than 99:59:59):
% .start = start time
% .current = current time
% .elapsed = elapsed time = etime(.current, .start)
% .estimated = estimated time
%
% DATEVEC = PROGRESSBAR('sec2time', T) is an internal function
% that returns a date vector DATEVEC corresponding T seconds.
%
% TIMESTR = PROGRESSBAR('time2str', DATEVEC) is an internal
% function that returns a 'HH:MM:SS' time string corresponding
% to DATEVEC. It tries to correct for min/sec overflows, and
% returns '??:??:??' if the DATEVEC = [0 0 0 0 0 0] or it
% contains negative values or is greater than 99:59:59.
%
% Created 2/7/2004 by:
% Ajay Nemani
% Center for Magnetic Resonance Research
% University of Illinois at Chicago
% aneman1@uic.edu
%
%
% %
%If no inputs given, assume create function
if nargin == 0
action = 'create';
end
switch lower(action)
case 'create'
%Create and intialize display
%useage: h = progressbar('create', 'Title', 'Message')
if nargin < 3
varargin{2} = 'Default Message';
end
if nargin < 2
varargin{1} = 'Default Title';
end
if ~ischar(varargin{1}) | ~ischar(varargin{2})
error('2nd and/or 3rd inputs must be strings');
end
%Initialize UserData structure
tmp = get(0, 'ScreenSize'); %This m-file was created on a 1600x1200 display,
ud.winscale = tmp(3)/1600; %so position vectors are scaled by winscale
ud.time.start = clock;
ud.time.current = clock;
ud.time.elapsed = progressbar('sec2time', ...
etime(ud.time.current, ud.time.start) ...
);
ud.time.estimated = [0 0 0 0 0 0];
ud.X = 0; %Fraction completed (forced to [0 1])
ud.stop = 0; %Stop signal
ud.refresh = .1; %Maximum refresh rate (in seconds)
%Create figure
ud.h.pbar = figure( ...
'Tag', 'Progress Bar', ...
'Resize', 'off', ...
'Color', get(0, 'DefaultUicontrolBackgroundColor'), ...
'DoubleBuffer', 'on', ...
'Selected', 'off', ...
'NumberTitle', 'off', ...
'IntegerHandle', 'off', ...
'MenuBar', 'none', ...
'Units', 'pixels', ...
'Position', ([500 500 600 150] * ud.winscale), ...
'Visible', 'on' ...
);
%Create message string
ud.h.message = uicontrol( ...
'Tag', 'Message Text', ...
'Style', 'text', ...
'TooltipString', '', ...
'Enable', 'on', ...
'Callback', [], ...
'Units', 'pixels', ...
'Position', ([5 85 590 60] * ud.winscale), ...
'Parent', ud.h.pbar, ...
'SelectionHighlight', 'off', ...
'Selected', 'off', ...
'FontName', 'FixedWidth', ...
'FontSize', get(0, 'DefaultUicontrolFontSize'), ...
'FontUnits', 'pixels', ...
'Visible', 'on' ...
);
%Create status string
ud.h.status = uicontrol( ...
'Tag', 'Status Text', ...
'Style', 'text', ...
'String', [], ...
'TooltipString', '', ...
'Enable', 'on', ...
'Callback', [], ...
'Units', 'pixels', ...
'Position', ([5 5 590 50] * ud.winscale), ...
'Parent', ud.h.pbar, ...
'SelectionHighlight', 'off', ...
'Selected', 'off', ...
'FontName', 'FixedWidth', ...
'FontSize', get(0, 'DefaultUicontrolFontSize'), ...
'FontUnits', 'pixels', ...
'Visible', 'on' ...
);
%Create progress bar axes
ud.h.axes = axes( ...
'Tag', 'Progress Bar Axes', ...
'Units', 'pixels', ...
'Box', 'on', ...
'Color', [1 1 1], ...
'DrawMode', 'fast', ...
'HitTest', 'off', ...
'Layer', 'top', ...
'NextPlot', 'ReplaceChildren', ...
'Parent', ud.h.pbar, ...
'Position', ([5 60 590 20] * ud.winscale), ...
'Selected', 'off', ...
'SelectionHighlight', 'off', ...
'TickLength', [.005 0], ...
'Visible', 'on', ...
'XLim', [0 1], ...
'XTick', [.25 .50 .75], ...
'XTickLabel', [], ...
'YLim', [0 2], ...
'YTick', [], ...
'YTickLabel', [], ...
'PlotBoxAspectRatio', [1 .025 1] ...
);
%Create progress bar filler - thick line works fastest
ud.h.line = line( ...
'Tag', 'Progress Bar Line', ...
'Color', [1 0 0], ...
'EraseMode', 'normal', ...
'HitTest', 'off', ...
'LineStyle', '-', ...
'LineWidth', 1000, ...
'Marker', 'none', ...
'Parent', ud.h.axes, ...
'Selected', 'off', ...
'SelectionHighlight', 'off', ...
'Visible', 'on', ...
'XData', [0 ud.X], ...
'YData', [1 1] ...
);
set(ud.h.pbar, 'UserData', ud);
%Set approprate fields by making initial update
progressbar('title', ud.h.pbar, varargin{1});
progressbar('message', ud.h.pbar, varargin{2});
progressbar('update', ud.h.pbar, ud.X);
varargout{1} = ud.h.pbar;
case 'cancelbutton'
%Create cancel button
%usage: progressbar('cancelbutton', h)
ud = get(varargin{1}, 'UserData');
%Create default callback
cancelcallback = ['set(get(gcbo, ''Parent''), ' ...
'''UserData'', ' ...
'setfield(get(get(gcbo, ''Parent''), ''UserData''), ' ...
'''stop'', ' ...
'1' ...
')' ...
'); ' ...
];
%Append user callback
if nargin == 3
cancelcallback = [cancelcallback varargin{2}];
end
%Shift all object in figure up
tmph=get(ud.h.pbar, 'Children');
tmppos = get(tmph, 'Position');
for n = 1:length(tmph)
set(tmph(n), ...
'Position', ...
(tmppos{n}/ud.winscale + [0 60 0 0]) * ud.winscale ...
);
end
clear tmph tmppos n;
%Resize figure to make room for cancel button
set(ud.h.pbar, ...
'Position', ...
(get(ud.h.pbar, 'Position')/ud.winscale + [0 0 0 60]) * ud.winscale ...
);
%Create cancel button in new space
ud.h.cancelbutton = uicontrol( ...
'Tag', 'Cancel Button', ...
'Style', 'pushbutton', ...
'String', 'Cancel', ...
'TooltipString', '', ...
'Enable', 'on', ...
'Callback', cancelcallback, ...
'Units', 'pixels', ...
'Position', ([200 10 200 50] * ud.winscale), ...
'Parent', ud.h.pbar, ...
'SelectionHighlight', 'off', ...
'Selected', 'off', ...
'FontName', 'FixedWidth', ...
'FontSize', 1, ...
'FontUnits', 'pixels', ...
'Visible', 'on' ...
);
drawnow;
set(ud.h.pbar, 'UserData', ud);
case 'update'
%update display (if necessary)
%usage: progressbar('update', h, X)
ud = get(varargin{1}, 'UserData');
%Calculate delta X and delta time
oldX = ud.X;
oldtime = ud.time.current;
newX = min(max(varargin{2}, 0), 1); %Force input X to be between 0 and 1
newtime = clock;
difftime = etime(newtime, oldtime);
diffX = newX - oldX;
%if last update was more than refresh rate seconds ago,
%or if X is 100% or 0%, then update, else do nothing
if (difftime >= ud.refresh) | (newX == 1) | (newX == 0)
%Update appropriate UserData fields
ud.time.current = newtime;
ud.time.elapsed = progressbar('sec2time', etime(ud.time.current, ud.time.start));
if diffX < eps %The estimate will be too large, so set to 0's -> ??:??:??
ud.time.estimated = [0 0 0 0 0 0];
else %Linear estimate
ud.time.estimated = progressbar('sec2time', (difftime/diffX)*(1-newX));
end
ud.X = newX;
set(ud.h.pbar, 'UserData', ud);
%Update status string and progress bar
statusstr = [ ...
'Start: ' ...
progressbar('time2str', ud.time.start) ...
sprintf('\t') ...
'Elapsed: ' ...
progressbar('time2str', ud.time.elapsed) ...
sprintf('\n') ...
'Estimated: ' ...
progressbar('time2str', ud.time.estimated) ...
sprintf('\t') ...
'Completed: ' ...
sprintf(' %06.2f%%', ud.X*100) ...
];
set(ud.h.line, 'XData', [0 ud.X]);
set(ud.h.status, 'String', statusstr);
drawnow;
end
case 'message'
%Change message
%usage: progressbar('message', h, 'Message')
ud = get(varargin{1}, 'UserData');
set(ud.h.message, 'String', varargin{2});
case 'title'
%Change title
%usage: progressbar('title', h, 'Title')
set(varargin{1}, 'Name', varargin{2});
case 'refresh'
%Change refresh rate (default is .1 sec)
%useage: progressbar('refresh', h, refreshrate)
ud = get(varargin{1}, 'UserData');
ud.refresh = varargin{2};
set(ud.h.pbar, 'UserData', ud);
case 'status'
%Get status (UserData) structure
%usage: status = progressbar('status', h)
%Yes, this is redundant, but it is included for completeness.
varargout{1} = get(varargin{1}, 'UserData');
case 'kill'
%Close progressbar
%usage: progressbar('kill', h)
%Yes, this is redundant as well...
close(varargin{1});
case 'sec2time'
%Convert seconds (usually from etime) to datevector
%usage: datevec = progressbar('sec2time', t)
%We don't care about month/day/year, so...
varargout{1} = [0 ...
0 ...
0 ...
floor(varargin{1}/3600) ...
mod(floor(varargin{1}/60), 60) ...
mod(varargin{1}, 60) ...
];
case 'time2str'
%Convert datevector to HH:MM:SS time string
%usage: timestr = progressbar('time2str', datevec)
if any(varargin{1}(5:6) > [60 60]) %Make corrections for min/sec overflow
varargin{1} = progressbar('sec2time', sum(varargin{1}(4:6) .* [3600 60 1]));
end
%Return '??:??:??' for invalid datevectors
if all(varargin{1}(4:6) == 0) ...
| any(varargin{1} < 0) ...
| any(varargin{1} > [Inf Inf Inf 99 60 60])
varargout{1} = ['??:??:??'];
else %If everything is legit, return HH:MM:SS string
varargout{1} = sprintf('%02.0f:%02.0f:%02.0f', ...
varargin{1}(4), ...
varargin{1}(5), ...
varargin{1}(6) ...
);
end
otherwise
error('Unrecognized action');
end