803 lines
28 KiB
Matlab
803 lines
28 KiB
Matlab
function cancel = multiWaitbar( label, varargin )
|
|
%multiWaitbar: add, remove or update an entry on the multi waitbar
|
|
%
|
|
% multiWaitbar(LABEL,VALUE) adds a waitbar for the specified label, or
|
|
% if it already exists updates the value. LABEL must be a string and
|
|
% VALUE a number between zero and one or the string 'Close' to remove the
|
|
% entry Setting value equal to 0 or 'Reset' will cause the progress bar
|
|
% to reset and the time estimate to be re-initialized.
|
|
%
|
|
% multiWaitbar(LABEL,COMMAND,VALUE,...) or
|
|
% multiWaitbar(LABEL,VALUE,COMMAND,VALUE,...)
|
|
% passes one or more command/value pairs for changing the named waitbar
|
|
% entry. Possible commands include:
|
|
% 'Value' Set the value of the named waitbar entry. The
|
|
% corresponding value must be a number between 0 and 1.
|
|
% 'Increment' Increment the value of the named waitbar entry. The
|
|
% corresponding value must be a number between 0 and 1.
|
|
% 'Color' Change the color of the named waitbar entry. The
|
|
% value must be an RGB triple, e.g. [0.1 0.2 0.3], or a
|
|
% single-character color name, e.g. 'r', 'b', 'm'.
|
|
% 'Relabel' Change the label of the named waitbar entry. The
|
|
% value must be the new name.
|
|
% 'Reset' Set the named waitbar entry back to zero and reset its
|
|
% timer. No value need be specified.
|
|
% 'CanCancel' [on|off] should a "cancel" button be shown for this bar
|
|
% (default 'off').
|
|
% 'CancelFcn' Function to call in the event that the user cancels.
|
|
% 'ResetCancel' Reset the "cancelled" flag for an entry (ie. if you
|
|
% decide not to cancel).
|
|
% 'Close' Remove the named waitbar entry.
|
|
% 'Busy' Puts this waitbar in "busy mode" where a small bar
|
|
% bounces back and forth. Return to normal progress display
|
|
% using the 'Reset' command.
|
|
%
|
|
% cancel = multiWaitbar(LABEL,VALUE) also returns whether the user has
|
|
% clicked the "cancel" button for this entry (true or false). Two
|
|
% mechanisms are provided for cancelling an entry if the 'CanCancel'
|
|
% setting is 'on'. The first is just to check the return argument and if
|
|
% it is true abort the task. The second is to set a 'CancelFcn' that is
|
|
% called when the user clicks the cancel button, much as is done for
|
|
% MATLAB's built-in WAITBAR. In either case, you can use the
|
|
% 'ResetCancel' command if you don't want to cancel after all.
|
|
%
|
|
% multiWaitbar('CLOSEALL') closes the waitbar window.
|
|
%
|
|
% Example:
|
|
% multiWaitbar( 'CloseAll' );
|
|
% multiWaitbar( 'Task 1', 0 );
|
|
% multiWaitbar( 'Task 2', 0.5, 'Color', 'b' );
|
|
% multiWaitbar( 'Task 3', 'Busy');
|
|
% multiWaitbar( 'Task 1', 'Value', 0.1 );
|
|
% multiWaitbar( 'Task 2', 'Increment', 0.2 );
|
|
% multiWaitbar( 'Task 3', 'Reset' ); % Disables "busy" mode
|
|
% multiWaitbar( 'Task 3', 'Value', 0.3 );
|
|
% multiWaitbar( 'Task 2', 'Close' );
|
|
% multiWaitbar( 'Task 3', 'Close' );
|
|
% multiWaitbar( 'Task 1', 'Close' );
|
|
%
|
|
% Example:
|
|
% multiWaitbar( 'Task 1', 0, 'CancelFcn', @(a,b) disp( ['Cancel ',a] ) );
|
|
% for ii=1:100
|
|
% abort = multiWaitbar( 'Task 1', ii/100 );
|
|
% if abort
|
|
% % Here we would normally ask the user if they're sure
|
|
% break
|
|
% else
|
|
% pause( 1 )
|
|
% end
|
|
% end
|
|
% multiWaitbar( 'Task 1', 'Close' )
|
|
%
|
|
% Example:
|
|
% multiWaitbar( 'CloseAll' );
|
|
% multiWaitbar( 'Red...', 7/7, 'Color', [0.8 0.0 0.1] );
|
|
% multiWaitbar( 'Orange...', 6/7, 'Color', [1.0 0.4 0.0] );
|
|
% multiWaitbar( 'Yellow...', 5/7, 'Color', [0.9 0.8 0.2] );
|
|
% multiWaitbar( 'Green...', 4/7, 'Color', [0.2 0.9 0.3] );
|
|
% multiWaitbar( 'Blue...', 3/7, 'Color', [0.1 0.5 0.8] );
|
|
% multiWaitbar( 'Indigo...', 2/7, 'Color', [0.4 0.1 0.5] );
|
|
% multiWaitbar( 'Violet...', 1/7, 'Color', [0.8 0.4 0.9] );
|
|
%
|
|
% Thanks to Jesse Hopkins for suggesting the "busy" mode.
|
|
|
|
% Author: Ben Tordoff
|
|
% Copyright 2007-2014 The MathWorks, Inc.
|
|
|
|
persistent FIGH;
|
|
cancel = false;
|
|
|
|
% Check basic inputs
|
|
error( nargchk( 1, inf, nargin ) ); %#ok<NCHKN> - kept for backwards compatibility
|
|
if ~ischar( label )
|
|
error( 'multiWaitbar:BadArg', 'LABEL must be the name of the progress entry (i.e. a string)' );
|
|
end
|
|
|
|
% Try to get hold of the figure
|
|
if isempty( FIGH ) || ~ishandle( FIGH )
|
|
FIGH = findall( 0, 'Type', 'figure', 'Tag', 'multiWaitbar:Figure' );
|
|
if isempty(FIGH)
|
|
FIGH = iCreateFig();
|
|
else
|
|
FIGH = handle( FIGH(1) );
|
|
end
|
|
end
|
|
|
|
% Check for close all and stop early
|
|
if any( strcmpi( label, {'CLOSEALL','CLOSE ALL'} ) )
|
|
iDeleteFigure(FIGH);
|
|
return;
|
|
end
|
|
|
|
% Make sure we're on-screen
|
|
if ~strcmpi( FIGH.Visible, 'on' )
|
|
FIGH.Visible = 'on';
|
|
end
|
|
|
|
% Get the list of entries and see if this one already exists
|
|
entries = getappdata( FIGH, 'ProgressEntries' );
|
|
if isempty(entries)
|
|
idx = [];
|
|
else
|
|
idx = find( strcmp( label, {entries.Label} ), 1, 'first' );
|
|
end
|
|
bgcol = getappdata( FIGH, 'DefaultProgressBarBackgroundColor' );
|
|
|
|
% If it doesn't exist, create it
|
|
needs_redraw = false;
|
|
entry_added = isempty(idx);
|
|
if entry_added
|
|
% Create a new entry
|
|
defbarcolor = getappdata( FIGH, 'DefaultProgressBarColor' );
|
|
entries = iAddEntry( FIGH, entries, label, 0, defbarcolor, bgcol );
|
|
idx = numel( entries );
|
|
end
|
|
|
|
% Check if the user requested a cancel
|
|
if nargout
|
|
cancel = entries(idx).Cancel;
|
|
end
|
|
|
|
% Parse the inputs. We shortcut the most common case as an efficiency
|
|
force_update = false;
|
|
if nargin==2 && isnumeric( varargin{1} )
|
|
entries(idx).LastValue = entries(idx).Value;
|
|
entries(idx).Value = max( 0, min( 1, varargin{1} ) );
|
|
entries(idx).Busy = false;
|
|
needs_update = true;
|
|
else
|
|
[params,values] = iParseInputs( varargin{:} );
|
|
|
|
needs_update = false;
|
|
for ii=1:numel( params )
|
|
switch upper( params{ii} )
|
|
case 'BUSY'
|
|
entries(idx).Busy = true;
|
|
needs_update = true;
|
|
|
|
case 'VALUE'
|
|
entries(idx).LastValue = entries(idx).Value;
|
|
entries(idx).Value = max( 0, min( 1, values{ii} ) );
|
|
entries(idx).Busy = false;
|
|
needs_update = true;
|
|
|
|
case {'INC','INCREMENT'}
|
|
entries(idx).LastValue = entries(idx).Value;
|
|
entries(idx).Value = max( 0, min( 1, entries(idx).Value + values{ii} ) );
|
|
entries(idx).Busy = false;
|
|
needs_update = true;
|
|
|
|
case {'COLOR','COLOUR'}
|
|
entries(idx).CData = iMakeColors( values{ii}, 16 );
|
|
needs_update = true;
|
|
force_update = true;
|
|
|
|
case {'RELABEL', 'UPDATELABEL'}
|
|
% Make sure we have a string as the value and that it
|
|
% doesn't already appear
|
|
if ~ischar( values{ii} )
|
|
error( 'multiWaitbar:BadString', 'Value for ''Relabel'' must be a string.' );
|
|
end
|
|
if ismember( values{ii}, {entries.Label} )
|
|
error( 'multiWaitbar:NameAlreadyExists', 'Cannot relabel an entry to a label that already exists.' );
|
|
end
|
|
entries(idx).Label = values{ii};
|
|
needs_update = true;
|
|
force_update = true;
|
|
|
|
case {'CANCANCEL'}
|
|
if ~ischar( values{ii} ) || ~any( strcmpi( values{ii}, {'on','off'} ) )
|
|
error( 'multiWaitbar:BadString', 'Parameter ''CanCancel'' must be a ''on'' or ''off''.' );
|
|
end
|
|
entries(idx).CanCancel = strcmpi( values{ii}, 'on' );
|
|
entries(idx).Cancel = false;
|
|
needs_redraw = true;
|
|
|
|
case {'RESETCANCEL'}
|
|
entries(idx).Cancel = false;
|
|
needs_redraw = true;
|
|
|
|
case {'CANCELFCN'}
|
|
if ~isa( values{ii}, 'function_handle' )
|
|
error( 'multiWaitbar:BadFunction', 'Parameter ''CancelFcn'' must be a valid function handle.' );
|
|
end
|
|
entries(idx).CancelFcn = values{ii};
|
|
if ~entries(idx).CanCancel
|
|
entries(idx).CanCancel = true;
|
|
end
|
|
needs_redraw = true;
|
|
|
|
case {'CLOSE','DONE'}
|
|
if ~isempty(idx)
|
|
% Remove the selected entry
|
|
entries = iDeleteEntry( entries, idx );
|
|
end
|
|
if isempty( entries )
|
|
iDeleteFigure( FIGH );
|
|
% With the window closed, there's nothing else to do
|
|
return;
|
|
else
|
|
needs_redraw = true;
|
|
end
|
|
% We can't continue after clearing the entry, so jump out
|
|
break;
|
|
|
|
otherwise
|
|
error( 'multiWaitbar:BadArg', 'Unrecognized command: ''%s''', params{ii} );
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
% Now work out what to update/redraw
|
|
if needs_redraw
|
|
setappdata( FIGH, 'ProgressEntries', entries );
|
|
iRedraw( FIGH );
|
|
% NB: Redraw includes updating all bars, so never need to do both
|
|
elseif needs_update
|
|
[entries(idx),needs_redraw] = iUpdateEntry( entries(idx), force_update );
|
|
setappdata( FIGH, 'ProgressEntries', entries );
|
|
% NB: if anything was updated onscreen, "needs_redraw" is now true.
|
|
end
|
|
if entry_added || needs_redraw
|
|
% If the shape or size has changed, do a full redraw, including events
|
|
drawnow();
|
|
end
|
|
|
|
% If we have any "busy" entries, start the timer, otherwise stop it.
|
|
myTimer = getappdata( FIGH, 'BusyTimer' );
|
|
if any([entries.Busy])
|
|
if strcmpi(myTimer.Running,'off')
|
|
start(myTimer);
|
|
end
|
|
else
|
|
if strcmpi(myTimer.Running,'on')
|
|
stop(myTimer);
|
|
end
|
|
end
|
|
|
|
end % multiWaitbar
|
|
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function [params, values] = iParseInputs( varargin )
|
|
% Parse the input arguments, extracting a list of commands and values
|
|
idx = 1;
|
|
params = {};
|
|
values = {};
|
|
if nargin==0
|
|
return;
|
|
end
|
|
if isnumeric( varargin{1} )
|
|
params{idx} = 'Value';
|
|
values{idx} = varargin{1};
|
|
idx = idx + 1;
|
|
end
|
|
|
|
while idx <= nargin
|
|
param = varargin{idx};
|
|
if ~ischar( param )
|
|
error( 'multiWaitbar:BadSyntax', 'Additional properties must be supplied as property-value pairs' );
|
|
end
|
|
params{end+1,1} = param; %#ok<AGROW>
|
|
values{end+1,1} = []; %#ok<AGROW>
|
|
switch upper( param )
|
|
case {'DONE','CLOSE','RESETCANCEL'}
|
|
% No value needed, and stop
|
|
break;
|
|
case {'BUSY'}
|
|
% No value needed but parsing should continue
|
|
idx = idx + 1;
|
|
case {'RESET','ZERO','SHOW'}
|
|
% All equivalent to saying ('Value', 0)
|
|
params{end} = 'Value';
|
|
values{end} = 0;
|
|
idx = idx + 1;
|
|
otherwise
|
|
if idx==nargin
|
|
error( 'multiWaitbar:BadSyntax', 'Additional properties must be supplied as property-value pairs' );
|
|
end
|
|
values{end,1} = varargin{idx+1};
|
|
idx = idx + 2;
|
|
end
|
|
end
|
|
if isempty( params )
|
|
error( 'multiWaitbar:BadSyntax', 'Must specify a value or a command' );
|
|
end
|
|
end % iParseInputs
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function fobj = iCreateFig()
|
|
% Create the progress bar group window
|
|
bgcol = get(0,'DefaultUIControlBackgroundColor');
|
|
f = figure( ...
|
|
'Name', 'Progress', ...
|
|
'Tag', 'multiWaitbar:Figure', ...
|
|
'Color', bgcol, ...
|
|
'MenuBar', 'none', ...
|
|
'ToolBar', 'none', ...
|
|
'WindowStyle', 'normal', ... % We don't want to be docked!
|
|
'HandleVisibility', 'off', ...
|
|
'IntegerHandle', 'off', ...
|
|
'Visible', 'off', ...
|
|
'NumberTitle', 'off' );
|
|
% Resize and centre on the first screen
|
|
screenSize = get(0,'ScreenSize');
|
|
figSz = [360 42];
|
|
figPos = ceil((screenSize(1,3:4)-figSz)/2);
|
|
fobj = handle( f );
|
|
fobj.Position = [figPos, figSz];
|
|
setappdata( fobj, 'ProgressEntries', [] );
|
|
% Make sure we have the image
|
|
defbarcolor = [0.8 0.0 0.1];
|
|
barbgcol = uint8( 255*0.75*bgcol );
|
|
setappdata( fobj, 'DefaultProgressBarBackgroundColor', barbgcol );
|
|
setappdata( fobj, 'DefaultProgressBarColor', defbarcolor );
|
|
setappdata( fobj, 'DefaultProgressBarSize', [350 16] );
|
|
% Create the timer to use for "Busy" mode, being sure to delete any
|
|
% existing ones
|
|
delete( timerfind('Tag', 'MultiWaitbarTimer') );
|
|
myTimer = timer( ...
|
|
'TimerFcn', @(src,evt) iTimerFcn(f), ...
|
|
'Period', 0.02, ...
|
|
'ExecutionMode', 'FixedRate', ...
|
|
'Tag', 'MultiWaitbarTimer' );
|
|
setappdata( fobj, 'BusyTimer', myTimer );
|
|
|
|
% Setup the resize function after we've finished setting up the figure to
|
|
% avoid excessive redraws
|
|
fobj.ResizeFcn = @iRedraw;
|
|
fobj.CloseRequestFcn = @iCloseFigure;
|
|
end % iCreateFig
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function cdata = iMakeColors( baseColor, height )
|
|
% Creates a shiny bar from a single base color
|
|
lightColor = [1 1 1];
|
|
badColorErrorID = 'multiWaitbar:BadColor';
|
|
badColorErrorMsg = 'Colors must be a three element vector [R G B] or a single character (''r'', ''g'' etc.)';
|
|
|
|
if ischar(baseColor)
|
|
switch upper(baseColor)
|
|
case 'K'
|
|
baseColor = [0.1 0.1 0.1];
|
|
case 'R'
|
|
baseColor = [0.8 0 0];
|
|
case 'G'
|
|
baseColor = [0 0.6 0];
|
|
case 'B'
|
|
baseColor = [0 0 0.8];
|
|
case 'C'
|
|
baseColor = [0.2 0.8 0.9];
|
|
case 'M'
|
|
baseColor = [0.6 0 0.6];
|
|
case 'Y'
|
|
baseColor = [0.9 0.8 0.2];
|
|
case 'W'
|
|
baseColor = [0.9 0.9 0.9];
|
|
otherwise
|
|
error( badColorErrorID, badColorErrorMsg );
|
|
end
|
|
else
|
|
if numel(baseColor) ~= 3
|
|
error( badColorErrorID, badColorErrorMsg );
|
|
end
|
|
if isa( baseColor, 'uint8' )
|
|
baseColor = double( baseColor ) / 255;
|
|
elseif isa( baseColor, 'double' )
|
|
if any(baseColor>1) || any(baseColor<0)
|
|
error( 'multiWaitbar:BadColorValue', 'Color values must be in the range 0 to 1 inclusive.' );
|
|
end
|
|
else
|
|
error( badColorErrorID, badColorErrorMsg );
|
|
end
|
|
end
|
|
|
|
% By this point we should have a double precision 3-element vector.
|
|
cols = repmat( baseColor, [height, 1] );
|
|
|
|
breaks = max( 1, round( height * [1 25 50 75 88 100] / 100 ) );
|
|
cols(breaks(1),:) = 0.6*baseColor;
|
|
cols(breaks(2),:) = lightColor - 0.4*(lightColor-baseColor);
|
|
cols(breaks(3),:) = baseColor;
|
|
cols(breaks(4),:) = min( baseColor*1.2, 1.0 );
|
|
cols(breaks(5),:) = min( baseColor*1.4, 0.95 ) + 0.05;
|
|
cols(breaks(6),:) = min( baseColor*1.6, 0.9 ) + 0.1;
|
|
|
|
y = 1:height;
|
|
cols(:,1) = max( 0, min( 1, interp1( breaks, cols(breaks,1), y, 'pchip' ) ) );
|
|
cols(:,2) = max( 0, min( 1, interp1( breaks, cols(breaks,2), y, 'pchip' ) ) );
|
|
cols(:,3) = max( 0, min( 1, interp1( breaks, cols(breaks,3), y, 'pchip' ) ) );
|
|
cdata = uint8( 255 * cat( 3, cols(:,1), cols(:,2), cols(:,3) ) );
|
|
end % iMakeColors
|
|
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function cdata = iMakeBackground( baseColor, height )
|
|
% Creates a shaded background
|
|
if isa( baseColor, 'uint8' )
|
|
baseColor = double( baseColor ) / 255;
|
|
end
|
|
|
|
ratio = 1 - exp( -0.5-2*(1:height)/height )';
|
|
cdata = uint8( 255 * cat( 3, baseColor(1)*ratio, baseColor(2)*ratio, baseColor(3)*ratio ) );
|
|
end % iMakeBackground
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function entries = iAddEntry( parent, entries, label, value, color, bgcolor )
|
|
% Add a new entry to the progress bar
|
|
|
|
% Create bar coloring
|
|
psize = getappdata( parent, 'DefaultProgressBarSize' );
|
|
cdata = iMakeColors( color, 16 );
|
|
% Create background image
|
|
barcdata = iMakeBackground( bgcolor, psize(2) );
|
|
|
|
% Work out the size in advance
|
|
labeltext = uicontrol( 'Style', 'Text', ...
|
|
'String', label, ...
|
|
'Parent', parent, ...
|
|
'HorizontalAlignment', 'Left' );
|
|
etatext = uicontrol( 'Style', 'Text', ...
|
|
'String', '', ...
|
|
'Parent', parent, ...
|
|
'HorizontalAlignment', 'Right' );
|
|
progresswidget = uicontrol( 'Style', 'Checkbox', ...
|
|
'String', '', ...
|
|
'Parent', parent, ...
|
|
'Position', [5 5 psize], ...
|
|
'CData', barcdata );
|
|
cancelwidget = uicontrol( 'Style', 'PushButton', ...
|
|
'String', '', ...
|
|
'FontWeight', 'Bold', ...
|
|
'Parent', parent, ...
|
|
'Position', [5 5 16 16], ...
|
|
'CData', iMakeCross( 8 ), ...
|
|
'Callback', @(src,evt) iCancelEntry( src, label ), ...
|
|
'Visible', 'off' );
|
|
mypanel = uipanel( 'Parent', parent, 'Units', 'Pixels' );
|
|
|
|
newentry = struct( ...
|
|
'Label', label, ...
|
|
'Value', value, ...
|
|
'LastValue', inf, ...
|
|
'Created', tic(), ...
|
|
'LabelText', labeltext, ...
|
|
'ETAText', etatext, ...
|
|
'ETAString', '', ...
|
|
'Progress', progresswidget, ...
|
|
'ProgressSize', psize, ...
|
|
'Panel', mypanel, ...
|
|
'BarCData', barcdata, ...
|
|
'CData', cdata, ...
|
|
'BackgroundCData', barcdata, ...
|
|
'CanCancel', false, ...
|
|
'CancelFcn', [], ...
|
|
'CancelButton', cancelwidget, ...
|
|
'Cancel', false, ...
|
|
'Busy', false );
|
|
if isempty( entries )
|
|
entries = newentry;
|
|
else
|
|
entries = [entries;newentry];
|
|
end
|
|
% Store in figure before the redraw
|
|
setappdata( parent, 'ProgressEntries', entries );
|
|
if strcmpi( get( parent, 'Visible' ), 'on' )
|
|
iRedraw( parent, [] );
|
|
else
|
|
set( parent, 'Visible', 'on' );
|
|
end
|
|
end % iAddEntry
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function entries = iDeleteEntry( entries, idx )
|
|
delete( entries(idx).LabelText );
|
|
delete( entries(idx).ETAText );
|
|
delete( entries(idx).CancelButton );
|
|
delete( entries(idx).Progress );
|
|
delete( entries(idx).Panel );
|
|
entries(idx,:) = [];
|
|
end % iDeleteEntry
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function entries = iCancelEntry( src, name )
|
|
figh = ancestor( src, 'figure' );
|
|
entries = getappdata( figh, 'ProgressEntries' );
|
|
if isempty(entries)
|
|
% The entries have been lost - nothing can be done.
|
|
return
|
|
end
|
|
idx = find( strcmp( name, {entries.Label} ), 1, 'first' );
|
|
|
|
% Set the cancel flag so that the user is told on next update
|
|
entries(idx).Cancel = true;
|
|
setappdata( figh, 'ProgressEntries', entries );
|
|
|
|
% If a user function is supplied, call it
|
|
if ~isempty( entries(idx).CancelFcn )
|
|
feval( entries(idx).CancelFcn, name, 'Cancelled' );
|
|
end
|
|
|
|
end % iCancelEntry
|
|
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function [entry,updated] = iUpdateEntry( entry, force )
|
|
% Update one progress bar
|
|
|
|
% Deal with busy entries separately
|
|
if entry.Busy
|
|
entry = iUpdateBusyEntry(entry);
|
|
updated = true;
|
|
return;
|
|
end
|
|
|
|
% Some constants
|
|
marker_weight = 0.8;
|
|
|
|
% Check if the label needs updating
|
|
updated = force;
|
|
val = entry.Value;
|
|
lastval = entry.LastValue;
|
|
|
|
% Now update the bar
|
|
psize = entry.ProgressSize;
|
|
filled = max( 1, round( val*psize(1) ) );
|
|
lastfilled = max( 1, round( lastval*psize(1) ) );
|
|
|
|
% We do some careful checking so that we only redraw what we have to. This
|
|
% makes a small speed difference, but every little helps!
|
|
if force || (filled<lastfilled)
|
|
% Create the bar background
|
|
startIdx = 1;
|
|
bgim = entry.BackgroundCData(:,ones( 1, psize(1)-filled ),:);
|
|
barim = iMakeBarImage(entry.CData, startIdx, filled);
|
|
progresscdata = [barim,bgim];
|
|
|
|
% Add light/shadow around the markers
|
|
markers = round( (0.1:0.1:val)*psize(1) );
|
|
markers(markers<startIdx | markers>(filled-2)) = [];
|
|
highlight = [marker_weight*entry.CData, 255 - marker_weight*(255-entry.CData)];
|
|
for ii=1:numel( markers )
|
|
progresscdata(:,markers(ii)+[-1,0],:) = highlight;
|
|
end
|
|
|
|
% Set the image into the checkbox
|
|
entry.BarCData = progresscdata;
|
|
set( entry.Progress, 'cdata', progresscdata );
|
|
updated = true;
|
|
|
|
elseif filled > lastfilled
|
|
% Just need to update the existing data
|
|
progresscdata = entry.BarCData;
|
|
startIdx = max(1,lastfilled-1);
|
|
% Repmat is the obvious way to fill the bar, but BSXFUN is often
|
|
% faster. Indexing is obscure but faster still.
|
|
progresscdata(:,startIdx:filled,:) = iMakeBarImage(entry.CData, startIdx, filled);
|
|
|
|
% Add light/shadow around the markers
|
|
markers = round( (0.1:0.1:val)*psize(1) );
|
|
markers(markers<startIdx | markers>(filled-2)) = [];
|
|
highlight = [marker_weight*entry.CData, 255 - marker_weight*(255-entry.CData)];
|
|
for ii=1:numel( markers )
|
|
progresscdata(:,markers(ii)+[-1,0],:) = highlight;
|
|
end
|
|
|
|
entry.BarCData = progresscdata;
|
|
set( entry.Progress, 'CData', progresscdata );
|
|
updated = true;
|
|
end
|
|
|
|
% As an optimization, don't update any text if the bar didn't move and the
|
|
% percentage hasn't changed
|
|
decval = round( val*100 );
|
|
lastdecval = round( lastval*100 );
|
|
|
|
if ~updated && (decval == lastdecval)
|
|
return
|
|
end
|
|
|
|
% Now work out the remaining time
|
|
minTime = 3; % secs
|
|
if val <= 0
|
|
% Zero value, so clear the eta
|
|
entry.Created = tic();
|
|
elapsedtime = 0;
|
|
etaString = '';
|
|
else
|
|
elapsedtime = round(toc( entry.Created )); % in seconds
|
|
|
|
% Only show the remaining time if we've had time to estimate
|
|
if elapsedtime < minTime
|
|
% Not enough time has passed since starting, so leave blank
|
|
etaString = '';
|
|
else
|
|
% Calculate a rough ETA
|
|
eta = elapsedtime * (1-val) / val;
|
|
etaString = iGetTimeString( eta );
|
|
end
|
|
end
|
|
|
|
if ~isequal( etaString, entry.ETAString )
|
|
set( entry.ETAText, 'String', etaString );
|
|
entry.ETAString = etaString;
|
|
updated = true;
|
|
end
|
|
|
|
% Update the label too
|
|
if force || elapsedtime > minTime
|
|
if force || (decval ~= lastdecval)
|
|
labelstr = [entry.Label, sprintf( ' (%d%%)', decval )];
|
|
set( entry.LabelText, 'String', labelstr );
|
|
updated = true;
|
|
end
|
|
end
|
|
|
|
end % iUpdateEntry
|
|
|
|
function eta = iGetTimeString( remainingtime )
|
|
if remainingtime > 172800 % 2 days
|
|
eta = sprintf( '%d days', round(remainingtime/86400) );
|
|
else
|
|
if remainingtime > 7200 % 2 hours
|
|
eta = sprintf( '%d hours', round(remainingtime/3600) );
|
|
else
|
|
if remainingtime > 120 % 2 mins
|
|
eta = sprintf( '%d mins', round(remainingtime/60) );
|
|
else
|
|
% Seconds
|
|
remainingtime = round( remainingtime );
|
|
if remainingtime > 1
|
|
eta = sprintf( '%d secs', remainingtime );
|
|
elseif remainingtime == 1
|
|
eta = '1 sec';
|
|
else
|
|
eta = ''; % Nearly done (<1sec)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end % iGetTimeString
|
|
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function entry = iUpdateBusyEntry( entry )
|
|
% Update a "busy" progress bar
|
|
% Make sure the widget is still OK
|
|
if ~ishandle(entry.Progress)
|
|
return
|
|
end
|
|
% Work out the new position. Since the bar is 0.1 long and needs to bounce,
|
|
% the position varies from 0 up to 0.9 then back down again. We achieve
|
|
% this with judicious use of "mod" with 1.8.
|
|
entry.Value = mod(entry.Value+0.01,1.8);
|
|
val = entry.Value;
|
|
if val>0.9
|
|
% Moving backwards
|
|
val = 1.8-val;
|
|
end
|
|
psize = entry.ProgressSize;
|
|
startIdx = max( 1, round( val*psize(1) ) );
|
|
endIdx = max( 1, round( (val+0.1)*psize(1) ) );
|
|
barLength = endIdx - startIdx + 1;
|
|
|
|
% Create the image
|
|
bgim = entry.BackgroundCData(:,ones( 1, psize(1) ),:);
|
|
barim = iMakeBarImage(entry.CData, 1, barLength);
|
|
bgim(:,startIdx:endIdx,:) = barim;
|
|
|
|
% Put it into the widget
|
|
entry.BarCData = bgim;
|
|
set( entry.Progress, 'CData', bgim );
|
|
end % iUpdateBusyEntry
|
|
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function barim = iMakeBarImage(strip, startIdx, endIdx)
|
|
shadow1_weight = 0.4;
|
|
shadow2_weight = 0.7;
|
|
barLength = endIdx - startIdx + 1;
|
|
% Repmat is the obvious way to fill the bar, but BSXFUN is often
|
|
% faster. Indexing is obscure but faster still.
|
|
barim = strip(:,ones(1, barLength),:);
|
|
% Add highlight to the start of the bar
|
|
if startIdx <= 2 && barLength>=2
|
|
barim(:,1,:) = 255 - shadow1_weight*(255-strip);
|
|
barim(:,2,:) = 255 - shadow2_weight*(255-strip);
|
|
end
|
|
% Add shadow to the end of the bar
|
|
if endIdx>=4 && barLength>=2
|
|
barim(:,end,:) = shadow1_weight*strip;
|
|
barim(:,end-1,:) = shadow2_weight*strip;
|
|
end
|
|
end % iMakeBarImage
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function iCloseFigure( fig, evt ) %#ok<INUSD>
|
|
% Closing the figure just makes it invisible
|
|
set( fig, 'Visible', 'off' );
|
|
end % iCloseFigure
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function iDeleteFigure( fig )
|
|
% Actually destroy the figure
|
|
busyTimer = getappdata( fig, 'BusyTimer' );
|
|
stop( busyTimer );
|
|
delete( busyTimer );
|
|
delete( fig );
|
|
end % iDeleteFigure
|
|
|
|
%-------------------------------------------------------------------------%
|
|
function iRedraw( fig, evt ) %#ok<INUSD>
|
|
entries = getappdata( fig, 'ProgressEntries' );
|
|
fobj = handle( fig );
|
|
p = fobj.Position;
|
|
% p = get( fig, 'Position' );
|
|
border = 5;
|
|
textheight = 16;
|
|
barheight = 16;
|
|
panelheight = 10;
|
|
N = max( 1, numel( entries ) );
|
|
|
|
% Check the height is correct
|
|
heightperentry = textheight+barheight+panelheight;
|
|
requiredheight = 2*border + N*heightperentry - panelheight;
|
|
if ~isequal( p(4), requiredheight )
|
|
p(2) = p(2) + p(4) - requiredheight;
|
|
p(4) = requiredheight;
|
|
% In theory setting the position should re-trigger this callback, but
|
|
% in practice it doesn't, probably because we aren't calling "drawnow".
|
|
set( fig, 'Position', p )
|
|
end
|
|
ypos = p(4) - border;
|
|
width = p(3) - 2*border;
|
|
setappdata( fig, 'DefaultProgressBarSize', [width barheight] );
|
|
|
|
for ii=1:numel( entries )
|
|
set( entries(ii).LabelText, 'Position', [border ypos-textheight width*0.75 textheight] );
|
|
set( entries(ii).ETAText, 'Position', [border+width*0.75 ypos-textheight width*0.25 textheight] );
|
|
ypos = ypos - textheight;
|
|
if entries(ii).CanCancel
|
|
set( entries(ii).Progress, 'Position', [border ypos-barheight width-barheight+1 barheight] );
|
|
entries(ii).ProgressSize = [width-barheight barheight];
|
|
set( entries(ii).CancelButton, 'Visible', 'on', 'Position', [p(3)-border-barheight ypos-barheight barheight barheight] );
|
|
else
|
|
set( entries(ii).Progress, 'Position', [border ypos-barheight width+1 barheight] );
|
|
entries(ii).ProgressSize = [width barheight];
|
|
set( entries(ii).CancelButton, 'Visible', 'off' );
|
|
end
|
|
ypos = ypos - barheight;
|
|
set( entries(ii).Panel, 'Position', [-500 ypos-500-panelheight/2 p(3)+1000 500] );
|
|
ypos = ypos - panelheight;
|
|
entries(ii) = iUpdateEntry( entries(ii), true );
|
|
end
|
|
setappdata( fig, 'ProgressEntries', entries );
|
|
end % iRedraw
|
|
|
|
function cdata = iMakeCross( sz )
|
|
% Create a cross-shape icon of size sz*sz*3
|
|
|
|
cdata = diag(ones(sz,1),0) + diag(ones(sz-1,1),1) + diag(ones(sz-1,1),-1);
|
|
cdata = cdata + flip(cdata,2);
|
|
|
|
% Convert zeros to nans (transparent) and non-zeros to zero (black)
|
|
cdata(cdata == 0) = nan;
|
|
cdata(~isnan(cdata)) = 0;
|
|
|
|
% Convert to RGB
|
|
cdata = cat( 3, cdata, cdata, cdata );
|
|
end % iMakeCross
|
|
|
|
|
|
function iTimerFcn(fig)
|
|
% Timer callback for updating stuff every so often
|
|
entries = getappdata( fig, 'ProgressEntries' );
|
|
for ii=1:numel(entries)
|
|
if entries(ii).Busy
|
|
entries(ii) = iUpdateBusyEntry(entries(ii));
|
|
end
|
|
end
|
|
setappdata( fig, 'ProgressEntries', entries );
|
|
end % iTimerFcn
|