11classdef dialogues
2+ % DIALOGUES This class is in charge of coordinating progress dialogues
3+ % within NeuroPAL_ID and across wrapped python scripts.
4+ %
5+ % Glossary:
6+ % - Task: This describes an active operation as represented in the
7+ % progress dialogue. Use this for any task that features several
8+ % steps. The dialogues class supports multiple nested tasks. If
9+ % you add a task while there is no active progress dialogue, one
10+ % will be created.
11+ % - Step: This describes a step within a tasks and are best used
12+ % within iterative loops. Adding any task or step will clear the
13+ % last step, and if you add a step while no progress dialogue is
14+ % active, we do not create one.
15+ % - Level filler: The progress dialogue system implemented through
16+ % the dialogues class supports nesting, meaning that child tasks
17+ % and steps are displayed as nested underneath their parent
18+ % tasks. Throughout this class, we use "level filler" to describe
19+ % the string that represents this nesting visually in the
20+ % progress dialogue. Basically, if, while performing Task A, you
21+ % start Task B, the progress dialogue would display as such:
22+ %
23+ % Task A
24+ % └🢒 Task B
25+ %
26+ % If you then add Another Task C underneath Task B, and then add
27+ % a step D, it will display as such:
28+ %
29+ % Task A
30+ % ├🢒 Task B
31+ % ├─🢒 Task C
32+ % └──🢒 { Step D }
33+ %
34+ % Here, Task B has no level filler, while Task C's level filler
35+ % is ─ and Step D's level filler is ──.
236
3- properties (Constant )
4- identifiers = struct( ...
5- ' task' , {" 🢒 " }, ...
6- ' step' , {" { " });
7-
37+ properties (Constant )
38+ % Text patterns used for sprintf() calls. The first %s describes
39+ % the nesting level of the given task/step, and the second %s
40+ % describes the task/step itself.
841 patterns = struct( ...
942 ' task' , {" └%s🢒 %s" }, ...
1043 ' step' , {" └%s{ %s }" });
44+
45+ identifiers = struct( ...
46+ ' task' , {" 🢒 " }, ...
47+ ' step' , {" { " });
1148 end
1249
1350 methods (Static , Access = public )
1451 function obj = active(input )
52+ % ACTIVE This function returns of the active dialogue if one
53+ % exists.
54+ %
55+ % Inputs:
56+ % - input: A handle referencing a uiprogressdlg instance.
57+ %
58+ % Outputs:
59+ % - obj: An active uiprogressdlg object. Empty if no progress
60+ % dialogue is active.
61+
62+ % Initialize a persistent variable.
1563 persistent handle
1664
65+ % If a dialogue handle was passed...
1766 if nargin > 0
67+ % Assign it to the persistent variable.
1868 handle = input ;
1969 end
2070
71+ % If the persistent variable does not reference a uiprogressdlg
72+ % object or is not a valid graphical object, clear the
73+ % persistent variable. This is to ensure that references to
74+ % deleted graphics objects are detected and cleared.
2175 if (isa(handle , " handle" ) && any(~isvalid(handle )))
2276 handle = [];
2377 end
2478
79+ % Return the persistent variable.
2580 obj = handle ;
2681 end
2782
3691 end
3792
3893 function handle = add_task(label )
94+ % ADD_TASK This function adds a new task to the progress
95+ % dialogue.
96+ %
97+ % Inputs:
98+ % - label: String/char representing text describing the task.
99+ % This is what will appear in the progress dialogue.
100+ %
101+ % Outputs:
102+ % - handle: A handle referencing the active progress
103+ % dialogue.
104+
105+ % Pass the label and task argument to update_message().
39106 handle = Program .GUI .dialogues .update_message(label , ' task' );
40107 end
41108
42109 function handle = step(label )
110+ % STEP This function adds a new step to the progress
111+ % dialogue.
112+ %
113+ % Inputs:
114+ % - label: String/char representing text describing the step.
115+ % This is what will appear in the progress dialogue.
116+ %
117+ % Outputs:
118+ % - handle: A handle referencing the active progress
119+ % dialogue.
120+
121+ % Pass the label and step argument to update_message().
43122 handle = Program .GUI .dialogues .update_message(label , ' step' );
44123 end
45124
@@ -78,68 +157,146 @@ function switch_to(mode, options)
78157 end
79158
80159 function handle = update_message(addl , template )
160+ % UPDATE_MESSAGE This function updates the active progress
161+ % dialogue with a passed task or step. If no progress
162+ % dialogue is active, this function calls the create()
163+ % function instead.
164+ %
165+ % Inputs:
166+ % - addl: String/char to be added to the progress dialogue.
167+ % - template: String/char specifying whether this is a task
168+ % or a step.
169+ %
170+ % Outputs:
171+ % - handle: Reference to the active progress dialogue.
172+
173+ % Get the active progress dialogue.
81174 handle = Program .GUI .dialogues .active();
82175
176+ % Check whether an active progress dialogue was returned...
83177 if ~isempty(handle )
178+
179+ % If so, split its current text into individual lines.
84180 arr_task = splitlines(handle .Message );
181+
182+ % Calculate the current number of lines.
85183 n_tasks = length(arr_task );
86184
185+ % If there is more than one line, update previous lines.
87186 if n_tasks > 1
187+ % Get the last line in the progress dialogue message.
88188 lline = arr_task{end };
89189
90190 if Program .GUI .dialogues .is_task(lline )
191+ % If the last line was a task, update its string
192+ % identifier to indicate that a nested operation is
193+ % in progress.
194+
195+ % Split the last line at its identifier.
91196 to_preserve = split(lline , " 🢒" );
197+
198+ % Calculate its level filler (i.e. one hyphen per
199+ % nested task).
92200 level_filler = repmat(' ─' , 1 , n_tasks - 2 );
201+
202+ % Construct the updated string.
93203 arr_task{end } = sprintf(" ├%s🢒%s" , ...
94204 level_filler , to_preserve{2 });
95205
96206 elseif Program .GUI .dialogues .is_step(lline )
207+ % If the last line was a step, delete it.
97208 arr_task(end ) = [];
209+
210+ % Update the number of lines in the progress
211+ % dialogue message accordingly.
98212 n_tasks = n_tasks - 1 ;
99213 end
100214 end
101215
216+ % Construct the level filler.
102217 level_filler = repmat(' ─' , 1 , n_tasks - 1 );
218+
219+ % Get the appropriate text pattern for this template.
103220 pattern = Program .GUI .dialogues .patterns.(template );
221+
222+ % Construct the new line and append it to the existing
223+ % message.
104224 arr_task{end + 1 } = sprintf(pattern , level_filler , addl );
105225
226+ % Update the progress dialogue's message property.
106227 handle.Message = sprintf(join(string(arr_task ), ' \n ' ));
107228
108229 elseif strcmpi(template , ' task' )
230+ % If no progress dialogue exists, create a new one and
231+ % initialize it with the passed task.
109232 handle = Program .GUI .dialogues .create(' progress' , ...
110233 ' Message' , addl );
111234 end
112235 end
113236
114237 function set_value(new_value )
238+ % SET_VALUE This function updates the value of the active
239+ % progress dialogue.
240+ %
241+ % Inputs:
242+ % - new_value: Non-negative numerical value to which the
243+ % progress dialogue's "Value" property will be updated.
244+
245+ % Get the active progress dialogue.
115246 handle = Program .GUI .dialogues .active();
247+
248+ % Check whether an active progress dialogue was found...
116249 if ~isempty(handle )
117250 if strcmp(handle .Indeterminate , ' on' )
251+ % If a progress dialogue was found but it is currently
252+ % set to indeterminate mode, change that.
118253 handle.Indeterminate = ' off' ;
254+
119255 elseif new_value == 1
256+ % If a progress dialogue was found but the passed value
257+ % is equal to 1 (suggesting a completed loop), switch
258+ % it to indeterminate mode.
120259 handle.Indeterminate = ' on' ;
260+
121261 else
262+ % If a progress dialogue was found and it is not
263+ % indeterminate, update its "Value" property with our
264+ % new value.
122265 handle.Value = new_value ;
123266 end
124267 end
125268 end
126269
127270 function resolve()
271+ % RESOLVE This function completes the last task/step passed to
272+ % the progress dialogue.
273+
274+ % Get the active progress dialogue.
128275 handle = Program .GUI .dialogues .active();
129276
277+ % Check to ensure that this is a valid progress dialogue.
130278 if isa(handle , " matlab.ui.dialog.ProgressDialog" )
131- task_arr = splitlines(Program .GUI .dialogues .clear_nlines(handle .Message ));
279+ % If it is, split its message into a cell array.
280+ task_arr = splitlines( ...
281+ Program .GUI .dialogues .clear_nlines(handle .Message ));
132282
133283 if length(task_arr ) < 2
284+ % If the line to be deleted is the only one left in the
285+ % message, delete the progress dialogue.
134286 delete(handle )
135287
136288 elseif Program .GUI .dialogues .is_step(task_arr{end })
289+ % If the last line was a step, delete it and then call
290+ % resolve() again.
291+
137292 task_arr(end ) = [];
138293 handle.Message = sprintf(join(string(task_arr ), ' \n ' ));
139294 Program .GUI .dialogues .resolve();
140295 return
141296
142297 else
298+ % If the last line was a step, delete it and update the
299+ % the level filler of any parent tasks.
143300 task_arr{end - 1 } = strrep(task_arr{end - 1 }, ' ├' , ' └' );
144301 new_message = join(task_arr(1 : end - 1 ), ' \n ' );
145302 handle.Message = sprintf(new_message{1 });
@@ -156,19 +313,37 @@ function resolve()
156313 end
157314
158315 function handle = create(mode , varargin )
316+ % CREATE This function creates a new dialogue with the passed
317+ % parameters.
318+ %
319+ % Inputs:
320+ % - mode: String/char describing the type of dialogue to be
321+ % created. We currently only support "progress".
322+ % - varargin: A variable-size cell array representing
323+ % key/value input arguments to be parsed by an
324+ % inputParser object.
325+
326+ % Create an inputParser.
159327 p = inputParser ;
160- addOptional(p , ' Message' , ' ' );
161- addOptional(p , ' Title' , Program .ProgramInfo .window().Name);
162- addOptional(p , ' Indeterminate' , ' on' );
163- addOptional(p , ' Options' , [" OK" , " Cancel" ])
164- addOptional(p , ' Cancelable' , ' off' );
328+
329+ % Define various optional arguments.
330+ addOptional(p , ' Message' , ' ' ); % This is the message our new dialogue will display. Empty by default.
331+ addOptional(p , ' Title' , Program .ProgramInfo .window().Name); % This is the title our new dialogue will use. Active window name by default.
332+ addOptional(p , ' Indeterminate' , ' on' ); % This is a switch describing whether the new dialogue will have indeterminate progress ("on") or discrete progress ("off"). On by default.
333+ addOptional(p , ' Options' , [" OK" , " Cancel" ]) % The options to be passed to a hypothetical choice dialogue to be created.
334+ addOptional(p , ' Cancelable' , ' off' ); % Whether the created dialogue can be canceled.
335+
336+ % Parse the described optional arguments using our inputParser.
165337 parse(p , varargin{: });
166338
339+ % Check which kind of dialogue should be created. We've only
340+ % implemented support for progress dialogues thus far.
167341 switch mode
168342 case ' instruction'
169343 handle = Program .GUI .dialogues .create_instruction(p .Results );
170344
171345 case ' progress'
346+ % Call create_progress() and pass the parsed arguments.
172347 handle = Program .GUI .dialogues .create_progress(p .Results );
173348
174349 case ' choice'
@@ -185,18 +360,47 @@ function resolve()
185360 return
186361 end
187362
363+ % Set the newly created dialogue to be the active dialogue.
188364 Program .GUI .dialogues .active(handle );
189365 end
190366 end
191367
192368 methods (Static , Access = private )
193369 function bool = is_task(str )
370+ % IS_TASK This function checks whether a passed text matches
371+ % the pattern we use for tasks.
372+ %
373+ % Inputs:
374+ % - str: String/char.
375+ %
376+ % Outputs:
377+ % - bool: Boolean describing whether the input text describes
378+ % a task or not. True if yes, false if no.
379+
380+ % Get the identifiers property.
194381 identifiers = Program .GUI .dialogues .identifiers ;
382+
383+ % Check whether the passed text matches what we expect to find
384+ % in a task string.
195385 bool = contains(str , identifiers .task );
196386 end
197387
198388 function bool = is_step(str )
389+ % IS_TASK This function checks whether a passed text matches
390+ % the pattern we use for steps.
391+ %
392+ % Inputs:
393+ % - str: String/char.
394+ %
395+ % Outputs:
396+ % - bool: Boolean describing whether the input text describes
397+ % a step or not. True if yes, false if no.
398+
399+ % Get the identifiers property.
199400 identifiers = Program .GUI .dialogues .identifiers ;
401+
402+ % Check whether the passed text matches what we expect to find
403+ % in a step string.
200404 bool = contains(str , identifiers .step );
201405 end
202406
@@ -218,9 +422,30 @@ function resolve()
218422 end
219423
220424 function cleared_string = clear_nlines(input )
425+ % CLEAR_NLINES This function replaces all empty lines from a
426+ % passed text and replaces it with a newline character ("\n").
427+ % Having this function avoids repeating this boilerplate code
428+ % in various places, mostly because the "Message" property of
429+ % certain dialogue objects includes empty lines.
430+ %
431+ % Inputs:
432+ % - input: String/char to be filtered.
433+ %
434+ % Outputs:
435+ % - cleared_string: The filtered input string.
436+
437+ % Split the input text into a cell array of individual lines.
221438 cleared_string = splitlines(input );
439+
440+ % Remove all elements of the resulting cell array that are
441+ % empty.
222442 cleared_string = cleared_string(~cellfun(' isempty' , cleared_string ));
443+
444+ % Join the remaining elements with the newline character ("\n")
445+ % as delimiter.
223446 cleared_string = join(cleared_string , ' \n ' );
447+
448+ % Call sprintf on the joined text to parse these new lines.
224449 cleared_string = sprintf(cleared_string{1 });
225450 end
226451 end
0 commit comments