0001 function cancel = multiWaitbar( label, varargin )
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073 persistent figh;
0074 cancel = false;
0075
0076
0077 error( nargchk( 1, inf, nargin ) );
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
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
0093 if any( strcmpi( label, {'CLOSEALL','CLOSE ALL'} ) )
0094 delete( figh );
0095 return;
0096 end
0097
0098
0099 if ~strcmpi( figh.Visible, 'on' )
0100 figh.Visible = 'on';
0101 end
0102
0103
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
0113 needs_redraw = false;
0114 entry_added = isempty(idx);
0115 if entry_added
0116
0117 defbarcolor = getappdata( figh, 'DefaultProgressBarColor' );
0118 entries = iAddEntry( figh, entries, label, 0, defbarcolor, bgcol );
0119 idx = numel( entries );
0120 end
0121
0122
0123 if nargout
0124 cancel = entries(idx).Cancel;
0125 end
0126
0127
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
0174 entries = iDeleteEntry( entries, idx );
0175 end
0176 if isempty( entries )
0177 delete( figh );
0178
0179 return;
0180 else
0181 needs_redraw = true;
0182 end
0183
0184 break;
0185
0186 otherwise
0187 error( 'multiWaitbar:BadArg', 'Unrecognised command: ''%s''', params{ii} );
0188
0189 end
0190 end
0191 end
0192
0193
0194 if needs_redraw
0195 setappdata( figh, 'ProgressEntries', entries );
0196 iRedraw( figh );
0197
0198 elseif needs_update
0199 [entries(idx),needs_redraw] = iUpdateEntry( entries(idx), force_update );
0200 setappdata( figh, 'ProgressEntries', entries );
0201
0202 end
0203 if entry_added || needs_redraw
0204
0205 drawnow();
0206 end
0207
0208 end
0209
0210
0211
0212 function [params, values] = iParseInputs( varargin )
0213
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;
0232 values{end+1,1} = [];
0233 switch upper( param )
0234 case {'DONE','CLOSE'}
0235
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
0253
0254
0255 function fobj = iCreateFig()
0256
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
0269 fobj = handle( f );
0270 fobj.Position(3:4) = [360 42];
0271 setappdata( fobj, 'ProgressEntries', [] );
0272
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
0279
0280 fobj.ResizeFcn = @iRedraw;
0281 fobj.CloseRequestFcn = @iCloseFigure;
0282 end
0283
0284
0285 function cdata = iMakeColors( baseColor, height )
0286
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
0308
0309
0310
0311 function cdata = iMakeBackground( baseColor, height )
0312
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
0320
0321
0322 function entries = iAddEntry( parent, entries, label, value, color, bgcolor )
0323
0324
0325
0326 psize = getappdata( parent, 'DefaultProgressBarSize' );
0327 cdata = iMakeColors( color, 16 );
0328
0329 barcdata = iMakeBackground( bgcolor, psize(2) );
0330
0331
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
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
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
0396
0397
0398 function entries = iCancelEntry( src, name )
0399 figh = ancestor( src, 'figure' );
0400 entries = getappdata( figh, 'ProgressEntries' );
0401 if isempty(entries)
0402
0403 return
0404 end
0405 idx = find( strcmp( name, {entries.Label} ), 1, 'first' );
0406
0407
0408 entries(idx).Cancel = true;
0409 setappdata( figh, 'ProgressEntries', entries );
0410
0411
0412 if ~isempty( entries(idx).CancelFcn )
0413 feval( entries(idx).CancelFcn, name, 'Cancelled' );
0414 end
0415
0416 end
0417
0418
0419
0420 function [entry,updated] = iUpdateEntry( entry, force )
0421
0422
0423
0424 marker_weight = 0.8;
0425 shadow1_weight = 0.4;
0426 shadow2_weight = 0.7;
0427
0428
0429 updated = force;
0430 val = entry.Value;
0431 lastval = entry.LastValue;
0432
0433
0434 psize = entry.ProgressSize;
0435 filled = max( 1, round( val*psize(1) ) );
0436 lastfilled = max( 1, round( lastval*psize(1) ) );
0437
0438
0439
0440 if force || (filled<lastfilled)
0441
0442 bgim = entry.BackgroundCData(:,ones( 1, psize(1)-filled ),:);
0443
0444
0445
0446 barim = entry.CData(:,ones( 1, filled ),:);
0447 progresscdata = [barim,bgim];
0448
0449
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
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
0465 entry.BarCData = progresscdata;
0466 set( entry.Progress, 'cdata', progresscdata );
0467 updated = true;
0468
0469 elseif filled > lastfilled
0470
0471 progresscdata = entry.BarCData;
0472 startidx = max(1,lastfilled-1);
0473
0474
0475 progresscdata(:,startidx:filled,:) = entry.CData(:,ones(1,filled-startidx+1),:);
0476
0477
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
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
0500 minTime = 3;
0501 if val <= 0
0502
0503 entry.Created = tic();
0504 elapsedtime = 0;
0505 eta = '';
0506 else
0507 elapsedtime = toc( entry.Created );
0508
0509
0510 if elapsedtime < minTime
0511
0512 eta = '';
0513 else
0514
0515 remainingtime = elapsedtime * (1-val) / val;
0516
0517 if remainingtime > 172800
0518 eta = sprintf( '%d days', round(remainingtime/86400) );
0519 else
0520 if remainingtime > 7200
0521 eta = sprintf( '%d hours', round(remainingtime/3600) );
0522 else
0523 if remainingtime > 120
0524 eta = sprintf( '%d mins', round(remainingtime/60) );
0525 else
0526
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 = '';
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
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
0558
0559
0560 function iCloseFigure( fig, evt )
0561
0562 set( fig, 'Visible', 'off' );
0563 end
0564
0565
0566 function iRedraw( fig, evt )
0567 entries = getappdata( fig, 'ProgressEntries' );
0568 fobj = handle( fig );
0569 p = fobj.Position;
0570
0571 border = 5;
0572 textheight = 16;
0573 barheight = 16;
0574 panelheight = 10;
0575 N = max( 1, numel( entries ) );
0576
0577
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
0584
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
0611
0612 function cdata = iMakeCross( sz )
0613
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
0629