-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwaittext.m
420 lines (394 loc) · 17.8 KB
/
waittext.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
function varargout=waittext(varargin)
%WAITTEXT Display updating line of text or textual waitbar in Command Window.
% WAITTEXT(X) updates the current line of the Command Window to display the
% numeric value X as 'X...'.
%
% WAITTEXT(MSG) updates the current line of the Command Window to display the
% text string MSG. MSG must be 80 or fewer characters in length.
%
% WAITTEXT(X,'waitbar') displays a textual waitbar of fractional length X.
%
% WAITTEXT(0,'init') initializes the wait text or textual waitbar. The wait
% text and textual waitbar must always be initialized before WAITTEXT(X,...)
% or WAITTEXT(MSG) is called. After initialization, any type of wait text or
% textual waitbar can be displayed. WAITTEXT does not need to be
% re-initialized in order to change the type of display output. However,
% WAITTEXT should be re-initalized if it is interupted by output to the
% Command Window from another source. Re-initializing outputs to a new line.
%
% WAITTEXT(X,MSGTYPE) displays the scalar value X in a preformatted message.
% The 'iteration' MSGTYPE displays positive integers as 'Iteration X...',
% while 'countdown' displays positive integers as 'X remaining...'. The
% 'percent' MSGTYPE displays numbers between 0 and 100 as 'X% complete...',
% while 'fraction' displays numbers between 0 and 1 as 'X% complete...'.
%
% WAITTEXT(X,'waitbar',OPTIONS) displays a textual waitbar of fractional
% length X and specifies a set of waitbar properties in OPTIONS. If OPTIONS is
% a scalar non-whitespace string, this character will be used as the waitbar
% 'indicator' instead of the default '|'. If OPTIONS is an integer between 1
% and 78 this value will be used as the 'length' of the waitbar instead of the
% default 50. If OPTIONS is a structure, valid field names are 'indicator',
% 'length', 'prefix', and 'suffix'. The 'prefix' and 'suffix' options prepend
% and append, respectively, text strings to the waitbar. Together they must be
% 28 or fewer characters in the case of the default waitbar length. If a
% waitbar 'length' of N is specified, then the 'prefix' and 'suffix' text
% strings must together be 78-N or fewer characters.
%
% WAITTEXT(0,'clear') clears the last wait message.
%
% WAITTEXT(...,'spmd',LABTARGET) displays an updating line of text or a
% textual waitbar from within an SPMD statement (SPMD and this option can only
% be used if you have Parallel Computing Toolbox). If LABTARGET is 'all', the
% input to WAITTEXT from all NUMLABS is summed (or concatenated with commas in
% the case of WAITTEXT(MSG)) before display. If LABTARGET is a LABINDEX from 1
% to NUMLABS, then only the input to WAITTEXT from that LABINDEX will be
% displayed. When using SPMD, warnings and other functions that print to the
% Command Window may cause unpredicatable output if WAITTEXT is not
% re-initialized after any interuption.
%
% MSG = WAITTEXT(...) outputs the text string of the wait text or textual
% waitbar that is displayed to the variable MSG.
%
% WAITTEXT is typically used inside of a FOR loop that performs a lengthy
% computation. WAITTEXT updates the current line very quickly. If a FOR loop
% takes less than 10 ms (0.01 sec.) to execute, it is reccomended that a
% textual waitbar be used or that WAITTEXT not be called every iteration in
% order for the updating text values to be more easily readable.
%
% Examples:
% waittext(0,'init')
% for i = 0:100
% % Computation here
% waittext(i,'percent')
% end
% waittext('Done.')
%
% opts = struct('indicator','>','length',20,'message','Progress: ');
% waittext(0,'init')
% for i = 0:100
% % Computation here
% waittext(i/100,'waitbar',opts)
% end
% waittext(0,'clear')
%
% matlabpool(2)
% spmd
% waittext(0,'init','spmd','all')
% for i = 0:100
% % Computation here
% waittext(i/100,'fraction','spmd','all')
% end
% waittext('Done.','spmd',1)
% end
% matlabpool close
%
% See also WAITBAR, FPRINTF, ISSTRPROP, DISP, SPMD.
% Andrew D. Horchler, horchler @ gmail . com, Created 6-8-12
% Revision: 1.0, 4-13-16
% Inspired by:
% blogs.mathworks.com/loren/2007/08/01/monitoring-progress-of-a-calculation/#7
% Note: WAITTEXT resets the LASTWARN function so that it will return an empty
% string matrix for both the last warning message and last message identifier in
% order to catch warnings that may occur between calls. When a warning is
% caught, an empty line of text is printed (equivalent to WAITTEXT being
% initialized) and LASTWARN is again reset.
narginchk(1,5);
nargoutchk(0,1);
% Check if spmd mode
hasSPMD = (exist('spmd','builtin') == 5);
if nargin > 2
isspmd = strcmpi(varargin{end-1},'spmd') && hasSPMD && numlabs > 1;
if isspmd
labtarget = varargin{end};
if any(strcmpi(labtarget,{'all','true','on','yes'}))
isAllLabs = true;
else
if ~isscalar(labtarget) || isempty(labtarget)
error('waittext:waittext:NonScalarLabTarget',...
'LABTARGET must be a non-empty scalar.');
end
if ~isnumeric(labtarget) || ~isreal(labtarget) ...
|| ~isfinite(labtarget) || labtarget < 1 ...
|| labtarget > numlabs || labtarget ~= floor(labtarget)
error('waittext:waittext:NonFiniteRealLabTarget',...
['LABTARGET must be a finite real integer between one '...
'and NUMLABS.']);
end
isAllLabs = false;
end
end
else
isspmd = false;
end
% Number of characters per line, not including new line ('\n')
nchars = 80;
% Check message value
if ischar(varargin{1})
msg = varargin{1};
if length(msg) > nchars
error('waittext:waittext:MessageTooLong',...
['Message string must be ' int2str(nchars) ' characters or '...
'less.']);
end
if ~isstrprop(msg,'print')
error('waittext:waittext:MessageInvalid',...
['Message string contains non-print characters. Control '...
'characters such as ''\t'',''\n'',''\r'',''\v'',''\f'', and '...
'''\b'' are not permitted.']);
end
if nargin > 1 && ~any(strcmpi(varargin{2},{'message','string','disp',...
'spmd'}))
error('waittext:waittext:InvalidMessageType',...
'String messages do not require a message type argument.');
end
msgtype = 'message';
% Global concatenation for spmd mode
if isspmd
if isAllLabs
msg = gcat([msg ', '],2);
if ~isempty(msg)
msg = msg(1:end-2);
end
else
msg = gop(@(x,y){x;y},msg,1);
if ~isempty(msg) && iscell(msg)
msg = msg{labtarget,:};
end
end
end
else
x = varargin{1};
if ~isscalar(x) || isempty(x)
error('waittext:waittext:NonScalarValue',...
'Value must be a non-empty scalar.');
end
if ~isnumeric(x) || ~isreal(x) || ~isfinite(x)
error('waittext:waittext:NonFiniteRealValue',...
'Value must be a finite real number.');
end
if nargin == 1
msgtype = 'default';
else
msgtype = lower(varargin{2});
end
% Global summation for spmd mode
if isspmd
if isAllLabs
x = gop(@plus,x/numlabs,1);
else
x = gcat(x,1,1);
if ~isempty(x)
x = x(labtarget);
end
end
end
end
% Add extra spaces if in spmd mode to remove formatting
sp = ' ';
spmdsp = double(isspmd)*2;
if hasSPMD && labindex == 1 || ~hasSPMD && ~isspmd
% Print message with carriage return in case of error or warning during wait
if any(strcmp(msgtype,{'init','initial','initialize','first'}))
if x ~= 0
error('waittext:waittext:NonZeroValueInit',...
['Value must be zero if the message type is ''clear'' or '...
'''init''.']);
end
% Set last warning so that warnings can be caught
lastwarn('');
% Full line of empty spaces so first update overwrites correct ammount
msg = [];
if isspmd
bs = char(8); % '\b'
fprintf(1,[bs(ones(1,10)) sp(ones(1,nchars+2)) '\n']);
else
fprintf(1,[sp(ones(1,nchars)) '\n']);
end
else
% Catch warnings so they are not overwritten
if ~isempty(lastwarn)
lastwarn('');
% Empty backspace array so warning on previous line not overwritten
bsa = [];
else
% Build backspace array to overwrite previous line (extra for spmd)
bs = char(8); % '\b'
bsa = bs(ones(1,nchars+1+double(isspmd)*4));
end
switch(msgtype)
case {'clear','delete'}
if x ~= 0
error('waittext:waittext:NonZeroValueClear',...
['Value must be zero if the message type is '...
'''clear'' or ''init''.']);
end
% Just backspace over last line
msg = [];
fprintf(1,[bsa sp(ones(1,spmdsp))]);
case 'countdown'
if x < 0 || x ~= floor(x)
error('waittext:waittext:InvalidCountdown',...
['Countdown values must be integers greater than '...
'or equal to zero.']);
end
num = int2str(x);
msg = [num ' remaining...'];
fprintf(1,[bsa msg sp(ones(1,nchars+spmdsp-13-length(num))) ...
'\n']);
case {'fraction','fractional','percent','percentage'}
if any(strcmp(msgtype,{'fraction','fractional'}))
if x < 0 || x > 1
error('waittext:waittext:InvalidFraction',...
'Fractional values must be between 0 and 1.');
end
pct = num2str(x*100,'%3.2f');
else
if x < 0 || x > 100
error('waittext:waittext:InvalidPercent',...
'Percentage values must be between 0 and 100.');
end
pct = num2str(x,'%3.2f');
end
% Regex to remove trailing decimal zeros, except for 0.0 case
if pct(end) == '0'
pct = regexprep(pct,'(\..*[^0])0*$|\.0*$()|^(0\.0)0*$',...
'$1');
end
ndigits = length(pct);
% Pass message as string in order to escape '%'
msg = [pct '% complete...'];
fprintf(1,[bsa sp(ones(1,ndigits-length(pct))) '%s' ...
sp(ones(1,nchars+spmdsp-13-ndigits)) '\n'],msg);
case {'iteration','count','counter'}
if x < 0 || x ~= floor(x)
error('waittext:waittext:InvalidIteration',...
['Iteration values must be integers greater than '...
'or equal to zero.']);
end
num = int2str(x);
msg = ['Iteration ' num '...'];
fprintf(1,[bsa msg sp(ones(1,nchars+spmdsp-13-length(num))) ...
'\n']);
case {'message','string','disp'}
% Pass message as string in order to escape possible '%' and '\'
fprintf(1,[bsa '%s' sp(ones(1,nchars+spmdsp-length(msg))) ...
'\n'],msg);
case {'waitbar','progressbar'}
indicator = '|';
nwait = 50;
premsg = '';
postmsg = '';
if nargin > 2 && ~strcmpi(varargin{3},'spmd')
s = varargin{3};
if isstruct(s)
f = lower(fieldnames(s));
for i = 1:length(f)
switch(f{i})
case {'indicator','char','character','marker'}
indicator = s.(f{i});
case {'length','len'}
nwait = s.(f{i});
case {'prefix','message','msg','text','txt'}
premsg = s.(f{i});
case {'suffix'}
postmsg = s.(f{i});
otherwise
error('waittext:waittext:UnknownWaitbarStructOption',...
['Unknown field in textual waitbar '...
'options structure. Valid fields '...
'are ''indicator'', ''length'', '...
'and ''message''.']);
end
end
elseif ischar(s)
indicator = s;
elseif isnumeric(s)
nwait = s;
else
error('waittext:waittext:UnknownWaitbarOption',...
['Unknown textual waitbar option. OPTIONS '...
'argument must be a scalar string or numeric '...
'value or a structure.']);
end
if ~isscalar(indicator) || isempty(indicator) ...
|| isspace(indicator)
error('waittext:waittext:InvalidWaitbarIndicator',...
['Optional waitbar indicator must be a single '...
'non-whitespace text character.']);
end
if ~isscalar(nwait) || isempty(nwait) || ~isreal(nwait) ...
|| ~isfinite(nwait)
error('waittext:waittext:NonScalarWaitbarLength',...
['Optional waitbar length must be a finite '...
'real scalar.']);
end
if nwait < 1 || nwait > nchars-2 || nwait ~= floor(nwait)
error('waittext:waittext:InvalidWaitbarLength',...
['Optional waitbar length must be an integer '...
'between 1 and ' int2str(nchars-2) '.']);
end
if ~ischar(premsg)
error('waittext:waittext:InvalidWaitbarPrefix',...
['Optional waitbar prefix must be a text '...
'string of %d or fewer characters.']);
end
if ~isstrprop(premsg,'print')
error('waittext:waittext:WaitbarPrefixInvalid',...
['Optional waitbar prefix string contains '...
'non-print characters. Control characters '...
'such as ''\t'',''\n'',''\r'',''\v'',''\f'', '...
'and ''\b'' are not permitted.']);
end
if ~ischar(postmsg)
error('waittext:waittext:InvalidWaitbarSuffix',...
['Optional waitbar suffix must be a text '...
'string of %d or fewer characters.']);
end
if ~isstrprop(postmsg,'print')
error('waittext:waittext:WaitbarSuffixInvalid',...
['Optional waitbar suffix string contains '...
'non-print characters. Control characters '...
'such as ''\t'',''\n'',''\r'',''\v'',''\f'', '...
'and ''\b'' are not permitted.']);
end
if length(premsg)+length(postmsg) > nchars-2-nwait
error('waittext:waittext:InvalidWaitbarMessageLength',...
['Optional waitbar prefix and suffix text '...
'strings must be %d or fewer characters.'],...
nchars-2-nwait);
end
end
if x < 0 || x > 1
error('waittext:waittext:InvalidWaitbarFraction',...
'Values must be between 0 and 1 for waitbar.');
end
% Pass message as string in order to escape possible '%' and '\'
nc = floor(x*nwait);
msg = [premsg '[' indicator(ones(1,nc)) sp(ones(1,nwait-nc)) ...
']' postmsg];
fprintf(1,[bsa '%s' sp(ones(1,nchars+spmdsp-nwait ...
-length(premsg)-length(postmsg)-2)) '\n'],msg);
otherwise
if nargin > 1 && ~strcmpi(varargin{2},'spmd')
error('waittext:waittext:UnkownMessageType',...
['Unknown message type. The message type string '...
'must be ''countdown'', ''fraction'', '...
'''iteration'', ''percent'', ''waitbar'', '...
'''spmd'', ''clear'', or ''init''.']);
end
num = num2str(x);
msg = [num '...'];
fprintf(1,[bsa msg sp(ones(1,nchars+spmdsp-3-length(num))) ...
'\n']);
end
end
% Handle variable output
if nargout == 1
varargout{1} = msg;
end
else
% Handle variable output
if nargout == 1
varargout{1} = [];
end
end