-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMouseManager.m
723 lines (591 loc) · 27.1 KB
/
MouseManager.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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
classdef MouseManager < handle
%MouseManager Create object to manage interactive mouse-based controls.
% MMOBJ = MouseManager(HFIGURE) will create a MouseManager object MMOBJ
% that provides a general-purpose interface for managing mouse-based
% interactions with figure objects. HFIGURE must be a valid figure
% handle. The lifecycle of MMOBJ is bound to HFIGURE; deleting HFIGURE
% will cause MMOBJ to be deleted as well.
%
% Graphics objects to be managed by MMOBJ, along with their associated
% callback functions, can be added using the MouseManager.add_item
% method. MMOBJ can be enabled/disabled using the MouseManager.enable
% method.
%
% See also handle, MouseManager.add_item, MouseManager.remove_item,
% MouseManager.default_hover_fcn, MouseManager.enable,
% MouseManager.delete.
% Author: Ken Eaton
% Version: MATLAB R2016b
% Last modified: 3/9/17
% Copyright 2017 by Kenneth P. Eaton
%--------------------------------------------------------------------------
%~~~Property blocks~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%------------------------------------------------------------------------
properties (SetAccess = immutable)
hFigure % Figure that MouseManager object is bound to.
end
%------------------------------------------------------------------------
properties (SetAccess = private)
enabled logical = false % Enabled state of MouseManager object.
itemList % List of managed graphics objects.
itemFcnTable % Structure of function handles.
defaultHoverFcn % Default function for hovering over figure.
end
%------------------------------------------------------------------------
properties (Access = private)
hListener % Listener for WindowButtonFcn properties.
hItemListeners % Listeners for managed graphics objects.
isActive logical = false % Indicates if a mouse button is active.
selectionType = 'none' % Mouse button selected.
figurePoint % Most recent figure CurrentPoint value.
itemIndex % Index of managed object currently active.
figureRegion % Figure-level position of managed object.
scrollEventData % Event data for scroll operations.
end
%~~~Event blocks~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%~~~Method blocks~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%------------------------------------------------------------------------
methods
%----------------------------------------------------------------------
function this = MouseManager(hFigure)
if (nargin > 0)
assert(ishandle(hFigure) && strcmpi(hFigure.Type, 'figure'), ...
'MouseManager:invalidFigureObject', ...
'Argument must be a valid figure object.');
this.hFigure = hFigure;
propertyNames = {'WindowButtonDownFcn', ...
'WindowButtonMotionFcn', ...
'WindowButtonUpFcn', ...
'WindowScrollWheelFcn'};
this.hListener = addlistener(hFigure, propertyNames, 'PreSet', ...
@(~, ~) this.enable(false));
this.hListener.Enabled = false;
addlistener(hFigure, 'ObjectBeingDestroyed', ...
@(~, ~) this.delete());
end
end
%----------------------------------------------------------------------
function enable(this, newState)
%enable Enable/disable a MouseManager object.
% MMOBJ.enable(NEWSTATE) will set the new enabled state of MMOBJ to
% NEWSTATE. NEWSTATE can be a logical (TRUE/FALSE) or character
% string ('on'/'off') input.
%
% When enabled, the WindowButtonDownFcn, WindowButtonMotionFcn,
% WindowButtonUpFcn, and WindowScrollWheelFcn properties of the
% linked figure will be updated for use by MMOBJ. If at any time
% these properties are modified, all of them will be removed and
% MMOBJ will be disabled.
%
% See also MouseManager.
% Check input and convert into a logical:
switch class(newState)
case 'logical'
assert(isscalar(newState), 'MouseManager:invalidInputSize', ...
'Logical input must be a scalar.');
case 'char'
newState = lower(newState);
assert(ismember(newState, {'on', 'off'}), ...
'MouseManager:invalidInputString', ...
'Input must be either ''on'' or ''off''.');
newState = strcmp(newState, 'on');
otherwise
try
newState = logical(newState);
catch
throw(MException('MouseManager:invalidInput', ...
'Could not convert input to logical.'));
end
assert(isscalar(newState), 'MouseManager:invalidInputSize', ...
'Logical input must be a scalar.');
end
% Add or remove figure callback functions as needed:
if (this.enabled ~= newState)
if newState
set(this.hFigure, ...
'WindowButtonDownFcn', {@this.mouse_op; 'down'}, ...
'WindowButtonMotionFcn', {@this.mouse_op; 'motion'}, ...
'WindowButtonUpFcn', {@this.mouse_op; 'up'}, ...
'WindowScrollWheelFcn', {@this.mouse_op; 'scroll'});
this.hListener.Enabled = true;
else
this.hListener.Enabled = false;
set(this.hFigure, 'WindowButtonDownFcn', '', ...
'WindowButtonMotionFcn', '', ...
'WindowButtonUpFcn', '', ...
'WindowScrollWheelFcn', '');
end
this.enabled = newState;
end
end
%----------------------------------------------------------------------
function add_item(this, hItem, varargin)
%add_item Add interactive mouse controls for an object.
% MMOBJ.add_item(H, (OPER), (SELECTION), CALLBACKFCN, ...) will add a
% graphics object H to a MouseManager object MMOBJ such that the
% function CALLBACKFCN will be invoked for a given mouse operation
% OPER and mouse selection SELECTION. The parent figure of H must
% match the figure MMOBJ is bound to. If H is already a managed
% object then CALLBACKFCN will overwrite any existing callback.
%
% OPER can be any one of: 'click', 'drag', 'release', 'hover', or
% 'scroll'. SELECTION can be any one of: 'normal' (left click),
% 'extend' (middle click), 'alt' (right click), or 'open' (double
% click). One or both of OPER or SELECTION can be a cell array
% containing a subset of the above values, in which case all
% combinations of OPER and SELECTION will invoke CALLBACKFCN. One or
% both of OPER or SELECTION can be omitted, in which case all
% possible respective values will be used.
%
% CALLBACKFCN must be written to accept two input arguments: a handle
% SOURCE and a structure EVENTDATA. SOURCE will be the handle H, and
% EVENTDATA will be a structure with the following fields:
% operation -- Mouse operation (values above)
% selectionType -- Mouse selection (values above, or 'none')
% figurePoint -- The CurrentPoint property of the bound
% figure when CALLBACKFCN is invoked
% figureRegion -- The position of H in pixels relative to the
% bound figure (from getpixelposition)
% scrollEventData -- Event data for scroll operations (empty
% when not scrolling)
% Making calls to drawnow from within CALLBACKFCN is strongly
% discouraged, as graphics refreshing is handled by the MouseManager
% class object. CALLBACKFCN can be a cell array where the first
% element is a function handle and the remaining elements are
% additional arguments to be passed to CALLBACKFCN. CALLBACKFCN can
% be empty, in which case any existing callback is cleared (and H is
% removed as a managed object if it has no associated callbacks).
%
% The input argument list can contain repeated sets of OPER,
% SELECTION, and CALLBACKFCN for setting more than one callback
% function for object H in a single call.
%
% If H is deleted at any time, then H and any callback functions
% associated with it through add_item will be removed from MMOBJ.
%
% See also MouseManager, MouseManager.remove_item, getpixelposition.
% Add the new graphics object if it is not in the list already:
assert(ishandle(hItem), 'MouseManager:invalidGraphicsObject', ...
'Object must be a valid graphics handle.');
assert(ancestor(hItem, 'figure') == this.hFigure, ...
'MouseManager:invalidGraphicsObject', ...
'Parent figure of graphics object must match bound figure.');
newList = this.itemList;
newFcnTable = this.itemFcnTable;
newListeners = this.hItemListeners;
index = find(hItem == newList);
isNewItem = isempty(index);
if isNewItem
newList = [newList; hItem];
newFcnTable = [newFcnTable; MouseManager.fcn_table_entry()];
newListeners = [newListeners; ...
addlistener(hItem, 'ObjectBeingDestroyed', ...
@(hItem, ~) this.remove_item(hItem))];
index = numel(newList);
end
% Parse input list:
callbackWasCleared = false;
while ~isempty(varargin)
inArgs = {{'click', 'drag', 'release', 'hover', 'scroll'}, ...
{'normal', 'extend', 'alt', 'open'}};
[varargin, inArgs] = MouseManager.parse_input(varargin, inArgs);
for oper = inArgs{1}
if isstruct(newFcnTable(index).(oper{1}))
for selection = inArgs{2}
newFcnTable(index).(oper{1}).(selection{1}) = inArgs{3};
end
else
newFcnTable(index).(oper{1}) = inArgs{3};
end
end
callbackWasCleared = callbackWasCleared || isempty(inArgs{3});
end
% Update managed object information:
this.itemFcnTable = newFcnTable;
if isNewItem
this.itemList = newList;
this.hItemListeners = newListeners;
end
% Perform clean-up if any callbacks were cleared:
if callbackWasCleared && isempty(newFcnTable(index).hover) ...
&& isempty(newFcnTable(index).scroll)
clickFcns = struct2cell([newFcnTable(index).click; ...
newFcnTable(index).drag; ...
newFcnTable(index).release]);
if all(cellfun('isempty', clickFcns(:)))
this.remove_item(hItem);
end
end
end
%----------------------------------------------------------------------
function remove_item(this, hRemove)
%remove_item Remove a managed object.
% MMOBJ.remove_item(H) will remove H as a managed object of MMOBJ.
% Any callbacks associated with H are removed. H can be a vector of
% graphics objects.
%
% See also MouseManager, MouseManager.add_item.
if ~this.isvalid()
return
end
index = ismember(this.itemList, hRemove);
this.itemList(index) = [];
this.itemFcnTable(index) = [];
delete(this.hItemListeners(index));
this.hItemListeners(index) = [];
end
%----------------------------------------------------------------------
function default_hover_fcn(this, hoverFcn)
%default_hover_fcn Add a default hover function.
% MMOBJ.default_hover_fcn(CALLBACKFCN) will add a callback function
% CALLBACKFCN to a MouseManager object MMOBJ to be evaluated when the
% mouse is hovering over the bound figure window but not over any
% other object managed by MMOBJ which has a callback defined for
% hovering or scrolling behavior.
%
% CALLBACKFCN must be written to accept two input arguments: a handle
% SOURCE and a structure EVENTDATA. SOURCE will be empty, and
% EVENTDATA will be a structure with the following fields:
% operation -- Mouse operation, set to 'hover'
% selectionType -- Mouse selection, set to 'none'
% figurePoint -- The CurrentPoint property of the bound
% figure when CALLBACKFCN is invoked
% figureRegion -- Empty
% scrollEventData -- Empty
% Making calls to drawnow from within CALLBACKFCN is strongly
% discouraged, as graphics refreshing is handled by the MouseManager
% class object. CALLBACKFCN can be a cell array where the first
% element is a function handle and the remaining elements are
% additional arguments to be passed to CALLBACKFCN. CALLBACKFCN can
% be empty, in which case any existing default hover function is
% removed.
%
% See also MouseManager, MouseManager.add_item.
if ~isempty(hoverFcn)
assert(isa(hoverFcn, 'function_handle') ...
|| (iscell(hoverFcn) ...
&& isa(hoverFcn{1}, 'function_handle')), ...
'MouseManager:invalidFunctionHandle', ...
'Function handle argument is invalid.');
end
this.defaultHoverFcn = hoverFcn;
end
%----------------------------------------------------------------------
function disp(this)
%disp Display method for MouseManager objects.
% disp(MMOBJ) displays information for the MouseManager object MMOBJ.
%
% See also MouseManager.
% Check object validity:
if ~this.isvalid()
link1 = ['matlab: helpview', ...
'([docroot ''/techdoc/matlab_oop/matlab_oop.map''],', ...
'''deleted_handle_objects'')'];
link2 = 'matlab: help MouseManager';
fprintf(' handle to %s %s\n\n', text2link('deleted', link1), ...
text2link('MouseManager', link2, 'font-weight:bold;'));
return
end
% Display general information:
fprintf(' MouseManager object:\n\n');
if isempty(this.defaultHoverFcn)
fcnString = '[]';
else
fcnString = callback2str(this.defaultHoverFcn);
end
displayData = {'hFigure', ['''', this.hFigure.Name, ''''], ...
'enabled', int2str(this.enabled), ...
'defaultHoverFcn', fcnString}.';
fprintf('%18s: %-s\n', displayData{:});
% Display managed items and associated callbacks:
fprintf('\n%23s | %-s\n', 'Item (Tag)', ...
'operation___selection___callbackFcn');
separator = [' ', repmat('-', 1, 73), '\n'];
separator(26) = '+';
oper = {'click__'; 'drag___'; 'release'};
selection = {'normal'; 'extend'; 'alt___'; 'open__'};
for index = 1:numel(this.itemList)
% Format data for click, drag, and release callbacks:
tableData = struct2cell([this.itemFcnTable(index).click; ...
this.itemFcnTable(index).drag; ...
this.itemFcnTable(index).release]);
tableData = [repmat({' | '}, 12, 1), ...
oper([1 1 1 1 2 2 2 2 3 3 3 3]), ...
repmat({' \_'}, 12, 1), ...
repmat(selection, 3, 1), ...
repmat({'___'}, 12, 1), ...
reshape(tableData(1:4, :), 12, 1)];
tableData(cellfun('isempty', tableData(:, 6)), :) = [];
tableData(:, 6) = cellfun(@(c) {callback2str(c)}, tableData(:, 6));
[~, startIndex] = unique(tableData(:, 2));
tableData(startIndex, 3) = {'___'};
tableData(startIndex, 1) = {' \_'};
tableData(setdiff(1:size(tableData, 1), startIndex), 2) = {''};
% Format data for hover callback:
hoverFcn = this.itemFcnTable(index).hover;
if ~isempty(hoverFcn)
tableData = [tableData; ...
{' \_', 'hover__', '___', ...
callback2str(hoverFcn), '', ''}]; %#ok<AGROW>
end
% Format data for scroll callback:
scrollFcn = this.itemFcnTable(index).scroll;
if ~isempty(scrollFcn)
tableData = [tableData; ...
{' \_', 'scroll_', '___', ...
callback2str(scrollFcn), '', ''}]; %#ok<AGROW>
end
% Final formatting of callback data:
tableData(1, 1) = {'___'};
tableData = [repmat({''}, 1, size(tableData, 1)); tableData.'];
tableData(1, 2:end) = {[blanks(25), '| ']};
% Display data for managed item:
fprintf(separator);
hItem = this.itemList(index);
if isempty(hItem.Tag)
itemString = hItem.Type;
else
itemString = [hItem.Type ' (' hItem.Tag ')'];
end
fprintf('%23s | %-s', itemString, ...
sprintf('%s%3s%7s%3s%6s%3s%s\n', tableData{:}));
end
fprintf('\n');
%--------------------------------------------------------------------
% Convert text to an HTML link.
function linkText = text2link(textString, textLink, textStyle)
if nargin == 2
textStyle = '';
end
linkText = sprintf('<a href="%s" style="%s">%s</a>', ...
textLink, textStyle, textString);
end
%--------------------------------------------------------------------
% Convert a callback to a string.
function callbackString = callback2str(callbackFcn)
if iscell(callbackFcn)
try
argString = cellfun(@(c) {[', ', char(c)]}, ...
callbackFcn(2:end));
catch
argString = {', ...'};
end
callbackString = ['{', func2str(callbackFcn{1}), ...
argString{:}, '}'];
if ~strcmp(callbackString(2), '@')
callbackString = ['{@', callbackString(2:end)];
end
else
callbackString = func2str(callbackFcn);
if ~strcmp(callbackString(1), '@')
callbackString = ['@', callbackString];
end
end
end
end
%----------------------------------------------------------------------
function delete(this)
%delete Delete a MouseManager object.
% delete(MMOBJ) deletes the MouseManager object MMOBJ. The object is
% deleted but is not cleared from the workspace. A deleted object is
% no longer valid.
%
% See also MouseManager, MouseManager.isvalid.
this.enable(false);
delete@handle(this);
end
end
%------------------------------------------------------------------------
methods (Access = private)
%----------------------------------------------------------------------
% Evaluate mouse operations.
function mouse_op(this, ~, eventData, mouseOperation)
switch mouseOperation
case 'down'
if (~this.isActive)
this.figurePoint = this.hFigure.CurrentPoint;
this.selectionType = this.hFigure.SelectionType;
this.scrollEventData = [];
if this.click_selected() || this.hover_selected()
this.isActive = true;
this.evaluate_operation('click');
drawnow limitrate
end
end
case 'motion'
this.figurePoint = this.hFigure.CurrentPoint;
if this.isActive
this.evaluate_operation('drag');
else
this.scrollEventData = [];
this.hover_selected();
this.evaluate_operation('hover');
end
drawnow limitrate
case 'up'
if this.isActive
this.figurePoint = this.hFigure.CurrentPoint;
this.evaluate_operation('drag');
this.evaluate_operation('release');
this.isActive = false;
this.selectionType = 'none';
this.hover_selected();
this.evaluate_operation('hover');
drawnow limitrate
end
case 'scroll'
if (~this.isActive)
this.figurePoint = this.hFigure.CurrentPoint;
this.scrollEventData = eventData;
this.hover_selected();
this.evaluate_operation('scroll');
drawnow limitrate
end
end
end
%----------------------------------------------------------------------
% Check if an item was last selected by clicking.
function clickSelected = click_selected(this)
this.itemIndex = [];
this.figureRegion = [];
if ~isempty(this.itemList) && ~isempty(this.hFigure.CurrentObject)
this.itemIndex = find(this.hFigure.CurrentObject == this.itemList);
end
clickSelected = ~isempty(this.itemIndex);
if clickSelected
clickObject = this.itemList(this.itemIndex);
this.figureRegion = getpixelposition(clickObject, true);
end
end
%----------------------------------------------------------------------
% Check if an item was last selected by hovering.
function hoverSelected = hover_selected(this)
this.itemIndex = [];
this.figureRegion = [];
for index = 1:numel(this.itemList)
hoverObject = this.itemList(index);
position = getpixelposition(hoverObject, true);
if all(this.figurePoint >= position(1:2)) && ...
all(this.figurePoint <= (position(1:2) + position(3:4)))
this.itemIndex = index;
this.figureRegion = position;
break
end
end
hoverSelected = ~isempty(this.itemIndex);
end
%----------------------------------------------------------------------
% Fetch and evaluate a mouse operation.
function evaluate_operation(this, oper)
if ~isempty(this.itemIndex)
fcn = this.itemFcnTable(this.itemIndex).(oper);
if isstruct(fcn)
fcn = fcn.(this.selectionType);
end
if isempty(fcn)
return
end
if iscell(fcn)
fcn{1}(this.itemList(this.itemIndex), this.event_data(oper), ...
fcn{2:end});
else
fcn(this.itemList(this.itemIndex), this.event_data(oper));
end
elseif strcmp(oper, 'hover') && ~isempty(this.defaultHoverFcn)
if iscell(this.defaultHoverFcn)
this.defaultHoverFcn{1}([], this.event_data(oper), ...
this.defaultHoverFcn{2:end});
else
this.defaultHoverFcn([], this.event_data(oper));
end
end
end
%----------------------------------------------------------------------
% Create an event data structure.
function eventData = event_data(this, oper)
eventData = struct('operation', oper, ...
'selectionType', this.selectionType, ...
'figurePoint', this.figurePoint, ...
'figureRegion', this.figureRegion, ...
'scrollEventData', this.scrollEventData);
end
end
%------------------------------------------------------------------------
methods (Access = private, Static)
%----------------------------------------------------------------------
% Create a new entry for itemFcnTable.
function newEntry = fcn_table_entry
selectionStruct = struct('normal', [], ...
'extend', [], ...
'alt', [], ...
'open', [], ...
'none', []);
newEntry = struct('click', selectionStruct, ...
'drag', selectionStruct, ...
'release', selectionStruct, ...
'hover', [], ...
'scroll', []);
end
%----------------------------------------------------------------------
% Parse input arguments.
function [argList, inArgs] = parse_input(argList, inArgs)
argIsSet = false(1, 2);
% Check up to 3 arguments from the argument list:
for inputIndex = 1:min(3, numel(argList))
% Check first for a function handle (or empty) argument:
newArg = argList{inputIndex};
if isempty(newArg) || isa(newArg, 'function_handle') ...
|| (iscell(newArg) && isa(newArg{1}, 'function_handle'))
inArgs{3} = newArg;
break
elseif (inputIndex == 3)
break
end
% Check and format character and cell array arguments:
switch class(newArg)
case 'char'
newArg = lower(newArg);
isValid = [ismember(newArg, inArgs{1}) ...
ismember(newArg, inArgs{2})];
validArgs = [inArgs{~argIsSet}];
assert(any(isValid), 'MouseManager:invalidArgumentString', ...
['Valid options for input arguments are: ' ...
sprintf('%s ', validArgs{:})]);
assert(~any(isValid & argIsSet), ...
'MouseManager:invalidFormat', ...
['Multiple values for OPER or SELECTION must be ' ...
'contained in a cell array.']);
inArgs{isValid} = {newArg};
argIsSet = argIsSet | isValid;
case 'cell'
assert(all(cellfun('isclass', newArg, 'char')), ...
'MouseManager:invalidArgumentType', ...
['Cell array input argument must be a cell array ' ...
'of character strings.']);
newArg = unique(lower(newArg(:).'));
isValid = [all(ismember(newArg, inArgs{1})) ...
all(ismember(newArg, inArgs{2}))];
validArgs = [inArgs{~argIsSet}];
assert(any(isValid), 'MouseManager:invalidArgumentString', ...
['Valid options for input arguments are: ' ...
sprintf('%s ', validArgs{:})]);
inArgs{inputIndex} = newArg;
argIsSet = argIsSet | isValid;
otherwise
throw(MException('MouseManager:invalidArgumentType', ...
['Input argument must be a character ' ...
'string, cell array of character ' ...
'strings, or function handle.']));
end
end
% Check that a function handle (or empty) argument exists:
if (numel(inArgs) < 3)
throw(MException('MouseManager:invalidInputFormat', ...
['Input argument list does not have the ' ...
'correct format.']));
end
% Shrink the argument list:
argList = argList((inputIndex+1):end);
end
end
end