multiWaitbar

PURPOSE ^

multiWaitbar: add, remove or update an entry on the multi waitbar

SYNOPSIS ^

function cancel = multiWaitbar( label, varargin )

DESCRIPTION ^

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-initialised.

   multiWaitbar(LABEL,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].
   '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.

   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 afterall. 

   multiWaitbar('CLOSEALL') closes the waitbar window.

   Example:
   multiWaitbar( 'CloseAll' );
   multiWaitbar( 'Task 1', 0 );
   multiWaitbar( 'Task 2', 0.5, 'Color', [0.2 0.6 0.2] );
   multiWaitbar( 'Task 1', 'Value', 0.1 );
   multiWaitbar( 'Task 2', 'Increment', 0.2 );
   multiWaitbar( 'Task 2', 'Close' );
   multiWaitbar( 'Task 1', 'Reset' );

   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] );

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

0001 function cancel = multiWaitbar( label, varargin )
0002 %multiWaitbar: add, remove or update an entry on the multi waitbar
0003 %
0004 %   multiWaitbar(LABEL,VALUE) adds a waitbar for the specified label, or
0005 %   if it already exists updates the value. LABEL must be a string and
0006 %   VALUE a number between zero and one or the string 'Close' to remove the
0007 %   entry Setting value equal to 0 or 'Reset' will cause the progress bar
0008 %   to reset and the time estimate to be re-initialised.
0009 %
0010 %   multiWaitbar(LABEL,COMMAND,VALUE,...) passes one or more command/value
0011 %   pairs for changing the named waitbar entry. Possible commands include:
0012 %   'Value'       Set the value of the named waitbar entry. The
0013 %                 corresponding value must be a number between 0 and 1.
0014 %   'Increment'   Increment the value of the named waitbar entry. The
0015 %                 corresponding value must be a number between 0 and 1.
0016 %   'Color'       Change the color of the named waitbar entry. The
0017 %                 value must be an RGB triple, e.g. [0.1 0.2 0.3].
0018 %   'Reset'       Set the named waitbar entry back to zero and reset its
0019 %                 timer. No value need be specified.
0020 %   'CanCancel'   [on|off] should a "cancel" button be shown for this bar
0021 %                 (default 'off').
0022 %   'CancelFcn'   Function to call in the event that the user cancels.
0023 %   'ResetCancel' Reset the "cancelled" flag for an entry (ie. if you
0024 %                 decide not to cancel).
0025 %   'Close'       Remove the named waitbar entry.
0026 %
0027 %   cancel = multiWaitbar(LABEL,VALUE) also returns whether the user has
0028 %   clicked the "cancel" button for this entry (true or false). Two
0029 %   mechanisms are provided for cancelling an entry if the 'CanCancel'
0030 %   setting is 'on'. The first is just to check the return argument and if
0031 %   it is true abort the task. The second is to set a 'CancelFcn' that is
0032 %   called when the user clicks the cancel button, much as is done for
0033 %   MATLAB's built-in WAITBAR. In either case, you can use the
0034 %   'ResetCancel' command if you don't want to cancel afterall.
0035 %
0036 %   multiWaitbar('CLOSEALL') closes the waitbar window.
0037 %
0038 %   Example:
0039 %   multiWaitbar( 'CloseAll' );
0040 %   multiWaitbar( 'Task 1', 0 );
0041 %   multiWaitbar( 'Task 2', 0.5, 'Color', [0.2 0.6 0.2] );
0042 %   multiWaitbar( 'Task 1', 'Value', 0.1 );
0043 %   multiWaitbar( 'Task 2', 'Increment', 0.2 );
0044 %   multiWaitbar( 'Task 2', 'Close' );
0045 %   multiWaitbar( 'Task 1', 'Reset' );
0046 %
0047 %   Example:
0048 %   multiWaitbar( 'Task 1', 0, 'CancelFcn', @(a,b) disp( ['Cancel ',a] ) );
0049 %   for ii=1:100
0050 %      abort = multiWaitbar( 'Task 1', ii/100 );
0051 %      if abort
0052 %         % Here we would normally ask the user if they're sure
0053 %         break
0054 %      else
0055 %         pause( 1 )
0056 %      end
0057 %   end
0058 %   multiWaitbar( 'Task 1', 'Close' )
0059 %
0060 %   Example:
0061 %   multiWaitbar( 'CloseAll' );
0062 %   multiWaitbar( 'Red...',    7/7, 'Color', [0.8 0.0 0.1] );
0063 %   multiWaitbar( 'Orange...', 6/7, 'Color', [1.0 0.4 0.0] );
0064 %   multiWaitbar( 'Yellow...', 5/7, 'Color', [0.9 0.8 0.2] );
0065 %   multiWaitbar( 'Green...',  4/7, 'Color', [0.2 0.9 0.3] );
0066 %   multiWaitbar( 'Blue...',   3/7, 'Color', [0.1 0.5 0.8] );
0067 %   multiWaitbar( 'Indigo...', 2/7, 'Color', [0.4 0.1 0.5] );
0068 %   multiWaitbar( 'Violet...', 1/7, 'Color', [0.8 0.4 0.9] );
0069 
0070 %   Author: Ben Tordoff
0071 %   Copyright 2007-2012 The MathWorks Ltd
0072 
0073 persistent figh;
0074 cancel = false;
0075 
0076 % Check basic inputs
0077 error( nargchk( 1, inf, nargin ) ); %#ok<NCHKN> - kept for backwards compatibility
0078 if ~ischar( label )
0079     error( 'multiWaitbar:BadArg', 'LABEL must be the name of the progress entry (i.e. a string)' );
0080 end
0081 
0082 % Try to get hold of the figure
0083 if isempty( figh ) || ~ishandle( figh )
0084     figh = findall( 0, 'Type', 'figure', 'Tag', 'multiWaitbar:Figure' );
0085     if isempty(figh)
0086         figh = iCreateFig();
0087     else
0088         figh = handle( figh(1) );
0089     end
0090 end
0091 
0092 % Check for close all and stop early
0093 if any( strcmpi( label, {'CLOSEALL','CLOSE ALL'} ) )
0094     delete( figh );
0095     return;
0096 end
0097 
0098 % Make sure we're on-screen
0099 if ~strcmpi( figh.Visible, 'on' )
0100     figh.Visible = 'on';
0101 end
0102 
0103 % Get the list of entries and see if this one already exists
0104 entries = getappdata( figh, 'ProgressEntries' );
0105 if isempty(entries)
0106     idx = [];
0107 else
0108     idx = find( strcmp( label, {entries.Label} ), 1, 'first' );
0109 end
0110 bgcol = getappdata( figh, 'DefaultProgressBarBackgroundColor' );
0111 
0112 % If it doesn't exist, create it
0113 needs_redraw = false;
0114 entry_added = isempty(idx);
0115 if entry_added
0116     % Create a new entry
0117     defbarcolor = getappdata( figh, 'DefaultProgressBarColor' );
0118     entries = iAddEntry( figh, entries, label, 0, defbarcolor, bgcol );
0119     idx = numel( entries );
0120 end
0121 
0122 % Check if the user requested a cancel
0123 if nargout
0124     cancel = entries(idx).Cancel;
0125 end
0126 
0127 % Parse the inputs. We shortcut the most common case as an efficiency
0128 force_update = false;
0129 if nargin==2 && isnumeric( varargin{1} )
0130     entries(idx).LastValue = entries(idx).Value;
0131     entries(idx).Value = max( 0, min( 1, varargin{1} ) );
0132     needs_update = true;
0133 else
0134     [params,values] = iParseInputs( varargin{:} );
0135     
0136     needs_update = false;
0137     for ii=1:numel( params )
0138         switch upper( params{ii} )
0139             case 'VALUE'
0140                 entries(idx).LastValue = entries(idx).Value;
0141                 entries(idx).Value = max( 0, min( 1, values{ii} ) );
0142                 needs_update = true;
0143                 
0144             case {'INC','INCREMENT'}
0145                 entries(idx).LastValue = entries(idx).Value;
0146                 entries(idx).Value = max( 0, min( 1, entries(idx).Value + values{ii} ) );
0147                 needs_update = true;
0148                 
0149             case {'COLOR','COLOUR'}
0150                 entries(idx).CData = iMakeColors( values{ii}, 16 );
0151                 needs_update = true;
0152                 force_update = true;
0153                 
0154             case {'CANCANCEL'}
0155                 if ~ischar( values{ii} ) || ~any( strcmpi( values{ii}, {'on','off'} ) )
0156                     error( 'multiWaitbar:BadString', 'Parameter ''CanCancel'' must be a ''on'' or ''off''.' );
0157                 end
0158                 entries(idx).CanCancel = strcmpi( values{ii}, 'on' );
0159                 needs_redraw = true;
0160                 
0161             case {'CANCELFCN'}
0162                 if ~isa( values{ii}, 'function_handle' )
0163                     error( 'multiWaitbar:BadFunction', 'Parameter ''CancelFcn'' must be a valid function handle.' );
0164                 end
0165                 entries(idx).CancelFcn = values{ii};
0166                 if ~entries(idx).CanCancel
0167                     entries(idx).CanCancel = true;
0168                 end
0169                 needs_redraw = true;
0170                 
0171             case {'CLOSE','DONE'}
0172                 if ~isempty(idx)
0173                     % Remove the selected entry
0174                     entries = iDeleteEntry( entries, idx );
0175                 end
0176                 if isempty( entries )
0177                     delete( figh );
0178                     % With the window closed, there's nothing else to do
0179                     return;
0180                 else
0181                     needs_redraw = true;
0182                 end
0183                 % We can't continue after clearing the entry, so jump out
0184                 break;
0185                 
0186             otherwise
0187                 error( 'multiWaitbar:BadArg', 'Unrecognised command: ''%s''', params{ii} );
0188                 
0189         end
0190     end
0191 end
0192 
0193 % Now work out what to update/redraw
0194 if needs_redraw
0195     setappdata( figh, 'ProgressEntries', entries );
0196     iRedraw( figh );
0197     % NB: Redraw includes updating all bars, so never need to do both
0198 elseif needs_update
0199     [entries(idx),needs_redraw] = iUpdateEntry( entries(idx), force_update );
0200     setappdata( figh, 'ProgressEntries', entries );
0201     % NB: if anything was updated onscreen, "needs_redraw" is now true.
0202 end
0203 if entry_added || needs_redraw
0204     % If the shape or size has changed, do a full redraw, including events
0205     drawnow();
0206 end
0207 
0208 end % multiWaitbar
0209 
0210 
0211 %-------------------------------------------------------------------------%
0212 function [params, values] = iParseInputs( varargin )
0213 % Parse the input arguments, extracting a list of commands and values
0214 idx = 1;
0215 params = {};
0216 values = {};
0217 if nargin==0
0218     return;
0219 end
0220 if isnumeric( varargin{1} )
0221     params{idx} = 'Value';
0222     values{idx} = varargin{1};
0223     idx = idx + 1;
0224 end
0225 
0226 while idx <= nargin
0227     param = varargin{idx};
0228     if ~ischar( param )
0229         error( 'multiWaitbar:BadSyntax', 'Additional properties must be supplied as property-value pairs' );
0230     end
0231     params{end+1,1} = param; %#ok<AGROW>
0232     values{end+1,1} = []; %#ok<AGROW>
0233     switch upper( param )
0234         case {'DONE','CLOSE'}
0235             % No value needed, and stop
0236             break;
0237         case {'RESET','ZERO','SHOW'}
0238             params{end} = 'Value';
0239             values{end} = 0;
0240             idx = idx + 1;
0241         otherwise
0242             if idx==nargin
0243                 error( 'multiWaitbar:BadSyntax', 'Additional properties must be supplied as property-value pairs' );
0244             end
0245             values{end,1} = varargin{idx+1};
0246             idx = idx + 2;
0247     end
0248 end
0249 if isempty( params )
0250     error( 'multiWaitbar:BadSyntax', 'Must specify a value or a command' );
0251 end
0252 end % iParseInputs
0253 
0254 %-------------------------------------------------------------------------%
0255 function fobj = iCreateFig()
0256 % Create the progress bar group window
0257 bgcol = get(0,'DefaultUIControlBackgroundColor');
0258 f = figure( ...
0259     'Name', 'Progress', ...
0260     'Tag', 'multiWaitbar:Figure', ...
0261     'Color', bgcol, ...
0262     'MenuBar', 'none', ...
0263     'ToolBar', 'none', ...
0264     'HandleVisibility', 'off', ...
0265     'IntegerHandle', 'off', ...
0266     'Visible', 'off', ...
0267     'NumberTitle', 'off' );
0268 % Resize
0269 fobj = handle( f );
0270 fobj.Position(3:4) = [360 42];
0271 setappdata( fobj, 'ProgressEntries', [] );
0272 % Make sure we have the image
0273 defbarcolor = [0.8 0.0 0.1];
0274 barbgcol = uint8( 255*0.75*bgcol );
0275 setappdata( fobj, 'DefaultProgressBarBackgroundColor', barbgcol );
0276 setappdata( fobj, 'DefaultProgressBarColor', defbarcolor );
0277 setappdata( fobj, 'DefaultProgressBarSize', [350 16] );
0278 % Setup the resize function after we've finished setting up the figure to
0279 % avoid excessive redraws
0280 fobj.ResizeFcn = @iRedraw;
0281 fobj.CloseRequestFcn = @iCloseFigure;
0282 end % iCreateFig
0283 
0284 %-------------------------------------------------------------------------%
0285 function cdata = iMakeColors( baseColor, height )
0286 % Creates a shiny bar from a single base color
0287 lightColor = [1 1 1];
0288 
0289 if isa( baseColor, 'uint8' )
0290     baseColor = double( baseColor ) / 255;
0291 end
0292 cols = repmat( baseColor, [height, 1] );
0293 
0294 breaks = max( 1, round( height * [1 25 50 75 88 100] / 100 ) );
0295 cols(breaks(1),:) = 0.6*baseColor;
0296 cols(breaks(2),:) = lightColor - 0.4*(lightColor-baseColor);
0297 cols(breaks(3),:) = baseColor;
0298 cols(breaks(4),:) = min( baseColor*1.2, 1.0 );
0299 cols(breaks(5),:) = min( baseColor*1.4, 0.95 ) + 0.05;
0300 cols(breaks(6),:) = min( baseColor*1.6, 0.9 ) + 0.1;
0301 
0302 y = 1:height;
0303 cols(:,1) = max( 0, min( 1, interp1( breaks, cols(breaks,1), y, 'cubic' ) ) );
0304 cols(:,2) = max( 0, min( 1, interp1( breaks, cols(breaks,2), y, 'cubic' ) ) );
0305 cols(:,3) = max( 0, min( 1, interp1( breaks, cols(breaks,3), y, 'cubic' ) ) );
0306 cdata = uint8( 255 * cat( 3, cols(:,1), cols(:,2), cols(:,3) ) );
0307 end % iMakeColors
0308 
0309 
0310 %-------------------------------------------------------------------------%
0311 function cdata = iMakeBackground( baseColor, height )
0312 % Creates a shaded background
0313 if isa( baseColor, 'uint8' )
0314     baseColor = double( baseColor ) / 255;
0315 end
0316 
0317 ratio = 1 - exp( -0.5-2*(1:height)/height )';
0318 cdata = uint8( 255 * cat( 3, baseColor(1)*ratio, baseColor(2)*ratio, baseColor(3)*ratio ) );
0319 end % iMakeBackground
0320 
0321 %-------------------------------------------------------------------------%
0322 function entries = iAddEntry( parent, entries, label, value, color, bgcolor )
0323 % Add a new entry to the progress bar
0324 
0325 % Create bar coloring
0326 psize = getappdata( parent, 'DefaultProgressBarSize' );
0327 cdata = iMakeColors( color, 16 );
0328 % Create background image
0329 barcdata = iMakeBackground( bgcolor, psize(2) );
0330 
0331 % Work out the size in advance
0332 labeltext = uicontrol( 'Style', 'Text', ...
0333     'String', label, ...
0334     'Parent', parent, ...
0335     'HorizontalAlignment', 'Left' );
0336 etatext = uicontrol( 'Style', 'Text', ...
0337     'String', '', ...
0338     'Parent', parent, ...
0339     'HorizontalAlignment', 'Right' );
0340 progresswidget = uicontrol( 'Style', 'Checkbox', ...
0341     'String', '', ...
0342     'Parent', parent, ...
0343     'Position', [5 5 psize], ...
0344     'CData', barcdata );
0345 cancelwidget = uicontrol( 'Style', 'PushButton', ...
0346     'String', '', ...
0347     'FontWeight', 'Bold', ...
0348     'Parent', parent, ...
0349     'Position', [5 5 16 16], ...
0350     'CData', iMakeCross( 8 ), ...
0351     'Callback', @(src,evt) iCancelEntry( src, label ), ...
0352     'Visible', 'off' );
0353 mypanel = uipanel( 'Parent', parent, 'Units', 'Pixels' );
0354 
0355 newentry = struct( ...
0356     'Label', label, ...
0357     'Value', value, ...
0358     'LastValue', inf, ...
0359     'Created', tic(), ...
0360     'LabelText', labeltext, ...
0361     'ETAText', etatext, ...
0362     'ETAString', '', ...
0363     'Progress', progresswidget, ...
0364     'ProgressSize', psize, ...
0365     'Panel', mypanel, ...
0366     'BarCData', barcdata, ...
0367     'CData', cdata, ...
0368     'BackgroundCData', barcdata, ...
0369     'CanCancel', false, ...
0370     'CancelFcn', [], ...
0371     'CancelButton', cancelwidget, ...
0372     'Cancel', false );
0373 if isempty( entries )
0374     entries = newentry;
0375 else
0376     entries = [entries;newentry];
0377 end
0378 % Store in figure before the redraw
0379 setappdata( parent, 'ProgressEntries', entries );
0380 if strcmpi( get( parent, 'Visible' ), 'on' )
0381     iRedraw( parent, [] );
0382 else
0383     set( parent, 'Visible', 'on' );
0384 end
0385 end % iAddEntry
0386 
0387 %-------------------------------------------------------------------------%
0388 function entries = iDeleteEntry( entries, idx )
0389 delete( entries(idx).LabelText );
0390 delete( entries(idx).ETAText );
0391 delete( entries(idx).CancelButton );
0392 delete( entries(idx).Progress );
0393 delete( entries(idx).Panel );
0394 entries(idx,:) = [];
0395 end % iDeleteEntry
0396 
0397 %-------------------------------------------------------------------------%
0398 function entries = iCancelEntry( src, name )
0399 figh = ancestor( src, 'figure' );
0400 entries = getappdata( figh, 'ProgressEntries' );
0401 if isempty(entries)
0402     % The entries have been lost - nothing can be done.
0403     return
0404 end
0405 idx = find( strcmp( name, {entries.Label} ), 1, 'first' );
0406 
0407 % Set the cancel flag so that the user is told on next update
0408 entries(idx).Cancel = true;
0409 setappdata( figh, 'ProgressEntries', entries );
0410 
0411 % If a user function is supplied, call it
0412 if ~isempty( entries(idx).CancelFcn )
0413     feval( entries(idx).CancelFcn, name, 'Cancelled' );
0414 end
0415 
0416 end % iCancelEntry
0417 
0418 
0419 %-------------------------------------------------------------------------%
0420 function [entry,updated] = iUpdateEntry( entry, force )
0421 % Update one progress bar
0422 
0423 % Some constants
0424 marker_weight = 0.8;
0425 shadow1_weight = 0.4;
0426 shadow2_weight = 0.7;
0427 
0428 % Check if the label needs updating
0429 updated = force;
0430 val = entry.Value;
0431 lastval = entry.LastValue;
0432 
0433 % Now update the bar
0434 psize = entry.ProgressSize;
0435 filled = max( 1, round( val*psize(1) ) );
0436 lastfilled = max( 1, round( lastval*psize(1) ) );
0437 
0438 % We do some careful checking so that we only redraw what we have to. This
0439 % makes a small speed difference, but every little helps!
0440 if force || (filled<lastfilled)
0441     % Create the bar background
0442     bgim = entry.BackgroundCData(:,ones( 1, psize(1)-filled ),:);
0443     % We use slightly bizarre indexing notation to achieve REPMAT of the
0444     % column at roughly 10x the speed. Blame Jon Cherrie (who showed me
0445     % that it was even faster than BSXFUN at doing REPMAT)!
0446     barim = entry.CData(:,ones( 1, filled ),:);
0447     progresscdata = [barim,bgim];
0448     
0449     % Add light/shadow around the markers
0450     markers = round( (0.1:0.1:val)*psize(1) );
0451     highlight = [marker_weight*entry.CData, 255 - marker_weight*(255-entry.CData)];
0452     for ii=1:numel( markers )
0453         progresscdata(:,markers(ii)+[-1,0],:) = highlight;
0454     end
0455     
0456     % Add highlight and shadow to the ends of the bar
0457     progresscdata(:,1,:) = 255 - shadow1_weight*(255-entry.CData);
0458     progresscdata(:,filled,:) = shadow1_weight*entry.CData;
0459     if filled>=4
0460         progresscdata(:,2,:) = 255 - shadow2_weight*(255-entry.CData);
0461         progresscdata(:,filled-1,:) = shadow2_weight*entry.CData;
0462     end
0463     
0464     % Set the image into the checkbox
0465     entry.BarCData = progresscdata;
0466     set( entry.Progress, 'cdata', progresscdata );
0467     updated = true;
0468     
0469 elseif filled > lastfilled
0470     % Just need to update the existing data
0471     progresscdata = entry.BarCData;
0472     startidx = max(1,lastfilled-1);
0473     % Repmat is the obvious way to fill the bar, but BSXFUN is often
0474     % faster. Indexing is obscure but faster still.
0475     progresscdata(:,startidx:filled,:) = entry.CData(:,ones(1,filled-startidx+1),:);
0476     
0477     % Add light/shadow around the markers
0478     markers = round( (0.1:0.1:val)*psize(1) );
0479     markers(markers<startidx) = [];
0480     highlight = [marker_weight*entry.CData, 255 - marker_weight*(255-entry.CData)];
0481     for ii=1:numel( markers )
0482         progresscdata(:,markers(ii)+[-1,0],:) = highlight;
0483     end
0484     
0485     % Add highlight and shadow to the ends of the bar
0486     if filled>2 && lastfilled<=3
0487         progresscdata(:,2,:) = 255 - shadow2_weight*(255-entry.CData);
0488         progresscdata(:,1,:) = 255 - shadow1_weight*(255-entry.CData);
0489     end
0490     progresscdata(:,filled,:) = shadow1_weight*entry.CData;
0491     if filled>=4
0492         progresscdata(:,filled-1,:) = shadow2_weight*entry.CData;
0493     end
0494     entry.BarCData = progresscdata;
0495     set( entry.Progress, 'CData', progresscdata );
0496     updated = true;
0497 end
0498 
0499 % Now work out the remaining time
0500 minTime = 3; % secs
0501 if val <= 0
0502     % Zero value, so clear the eta
0503     entry.Created = tic();
0504     elapsedtime = 0;
0505     eta = '';
0506 else
0507     elapsedtime = toc( entry.Created ); % in seconds
0508     
0509     % Only show the remaining time if we've had time to estimate
0510     if elapsedtime < minTime
0511         % Not enough time has passed since starting, so leave blank
0512         eta = '';
0513     else
0514         % Calculate a rough ETA
0515         remainingtime = elapsedtime * (1-val) / val;
0516         
0517         if remainingtime > 172800 % 2 days
0518             eta = sprintf( '%d days', round(remainingtime/86400) );
0519         else
0520             if remainingtime > 7200 % 2 hours
0521                 eta = sprintf( '%d hours', round(remainingtime/3600) );
0522             else
0523                 if remainingtime > 120 % 2 mins
0524                     eta = sprintf( '%d mins', round(remainingtime/60) );
0525                 else
0526                     % Seconds
0527                     remainingtime = round( remainingtime );
0528                     if remainingtime > 1
0529                         eta = sprintf( '%d secs', remainingtime );
0530                     elseif remainingtime == 1
0531                         eta = '1 sec';
0532                     else
0533                         eta = ''; % Nearly done (<1sec)
0534                     end
0535                 end
0536             end
0537         end
0538     end
0539 end
0540 
0541 if ~isequal( eta, entry.ETAString )
0542     set( entry.ETAText, 'String', eta );
0543     entry.ETAString = eta;
0544     updated = true;
0545 end
0546 
0547 % Update the label too
0548 if elapsedtime > minTime
0549     decval = round( val*100 );
0550     if force || (decval ~= round( lastval*100 ))
0551         labelstr = [entry.Label, sprintf( ' (%d%%)', decval )];
0552         set( entry.LabelText, 'String', labelstr );
0553         updated = true;
0554     end
0555 end
0556 
0557 end % iUpdateEntry
0558 
0559 %-------------------------------------------------------------------------%
0560 function iCloseFigure( fig, evt ) %#ok<INUSD>
0561 % Closing the figure just makes it invisible
0562 set( fig, 'Visible', 'off' );
0563 end % iCloseFigure
0564 
0565 %-------------------------------------------------------------------------%
0566 function iRedraw( fig, evt ) %#ok<INUSD>
0567 entries = getappdata( fig, 'ProgressEntries' );
0568 fobj = handle( fig );
0569 p = fobj.Position;
0570 % p = get( fig, 'Position' );
0571 border = 5;
0572 textheight = 16;
0573 barheight = 16;
0574 panelheight = 10;
0575 N = max( 1, numel( entries ) );
0576 
0577 % Check the height is correct
0578 heightperentry = textheight+barheight+panelheight;
0579 requiredheight = 2*border + N*heightperentry - panelheight;
0580 if ~isequal( p(4), requiredheight )
0581     p(2) = p(2) + p(4) - requiredheight;
0582     p(4) = requiredheight;
0583     % In theory setting the position should re-trigger this callback, but
0584     % in practice it doesn't, probably because we aren't calling "drawnow".
0585     set( fig, 'Position', p )
0586 end
0587 ypos = p(4) - border;
0588 width = p(3) - 2*border;
0589 setappdata( fig, 'DefaultProgressBarSize', [width barheight] );
0590 
0591 for ii=1:numel( entries )
0592     set( entries(ii).LabelText, 'Position', [border ypos-textheight width*0.75 textheight] );
0593     set( entries(ii).ETAText, 'Position', [border+width*0.75 ypos-textheight width*0.25 textheight] );
0594     ypos = ypos - textheight;
0595     if entries(ii).CanCancel
0596         set( entries(ii).Progress, 'Position', [border ypos-barheight width-barheight+1 barheight] );
0597         entries(ii).ProgressSize = [width-barheight barheight];
0598         set( entries(ii).CancelButton, 'Visible', 'on', 'Position', [p(3)-border-barheight ypos-barheight barheight barheight] );
0599     else
0600         set( entries(ii).Progress, 'Position', [border ypos-barheight width+1 barheight] );
0601         entries(ii).ProgressSize = [width barheight];
0602         set( entries(ii).CancelButton, 'Visible', 'off' );
0603     end
0604     ypos = ypos - barheight;
0605     set( entries(ii).Panel, 'Position', [-500 ypos-500-panelheight/2 p(3)+1000 500] );
0606     ypos = ypos - panelheight;
0607     entries(ii) = iUpdateEntry( entries(ii), true );
0608 end
0609 setappdata( fig, 'ProgressEntries', entries );
0610 end % iRedraw
0611 
0612 function cdata = iMakeCross( sz )
0613 % Create a cross-shape image
0614 
0615 cdata = nan( sz, sz );
0616 for ii=1:sz
0617     cdata(ii,ii) = 0;
0618     cdata(sz-ii+1,ii) = 0;
0619 end
0620 for ii=2:sz
0621     cdata(ii,ii-1) = 0;
0622     cdata(ii-1,ii) = 0;
0623     cdata(sz-ii+1,ii-1) = 0;
0624     cdata(ii,sz-ii+2) = 0;
0625 end
0626 cdata = cat( 3, cdata, cdata, cdata );
0627 
0628 end % iMakeCross
0629

Community support and wiki are available on Redmine. Last update: 18-Apr-2019.