Skip to content

Commit 8272c01

Browse files
committed
Added performance test and tooltip strings
1 parent d9fc48d commit 8272c01

File tree

5 files changed

+270
-22
lines changed

5 files changed

+270
-22
lines changed

+eui/ConditionPanel.m

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,18 @@
101101
end
102102

103103
function onEdit(obj, src, eventData)
104+
% ONEDIT Callback for edits to condition table
105+
% Updates the underlying parameter struct, changes the UI table
106+
% data. The src object should be the UI Table that has been edited,
107+
% and eventData contains the table indices of the edited cell.
108+
%
109+
% See also FILLCONDITIONTABLE, EUI.PARAMEDITOR/UPDATE
104110
disp('updating table cell');
105111
row = eventData.Indices(1);
106112
col = eventData.Indices(2);
107-
paramName = obj.ConditionTable.ColumnName{col};
113+
assert(all(cellfun(@strcmpi, strrep(obj.ConditionTable.ColumnName, ' ', ''), ...
114+
obj.ParamEditor.Parameters.TrialSpecificNames)), 'Unexpected condition names')
115+
paramName = obj.ParamEditor.Parameters.TrialSpecificNames{col};
108116
newValue = obj.ParamEditor.update(paramName, eventData.NewData, row);
109117
reformed = obj.ParamEditor.paramValue2Control(newValue);
110118
% If successful update the cell with default formatting
@@ -163,10 +171,10 @@ function makeGlobal(obj)
163171
disp('nothing selected')
164172
return
165173
end
174+
PE = obj.ParamEditor;
166175
[cols, iu] = unique(obj.SelectedCells(:,2));
167-
names = obj.ConditionTable.ColumnName(cols);
176+
names = PE.Parameters.TrialSpecificNames(cols);
168177
rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols
169-
PE = obj.ParamEditor;
170178
cellfun(@PE.globaliseParamAtCell, names, rows);
171179
% If only numRepeats remains, globalise it
172180
if isequal(PE.Parameters.TrialSpecificNames, {'numRepeats'})
@@ -180,7 +188,6 @@ function deleteSelectedConditions(obj)
180188
% The callback for the 'Delete condition' button. This removes the
181189
% selected conditions from the table and if less than two conditions
182190
% remain, globalizes them.
183-
% TODO: comment function better, index in a clearer fashion
184191
%
185192
% See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS
186193
rows = unique(obj.SelectedCells(:,1));
@@ -277,8 +284,9 @@ function fillConditionTable(obj)
277284
% Populates the UI Table with trial specific parameters, where each
278285
% row is a trial condition (that is, a parameter column) and each
279286
% column is a different trial specific parameter
280-
titles = obj.ParamEditor.Parameters.TrialSpecificNames;
281-
[~, trialParams] = obj.ParamEditor.Parameters.assortForExperiment;
287+
P = obj.ParamEditor.Parameters;
288+
titles = P.title(P.TrialSpecificNames);
289+
[~, trialParams] = P.assortForExperiment;
282290
if isempty(titles)
283291
obj.ButtonPanel.Visible = 'off';
284292
obj.UIPanel.Visible = 'off';

+eui/FieldPanel.m

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
% Maximum allowable width (in pixels) for each UIControl element
1010
MaxCtrlWidth = 140
1111
% Space (in pixels) between parent container and parameter fields
12-
Margin = 14
12+
Margin = 4
1313
% Space (in pixels) between each parameter field row
1414
RowSpacing = 1
1515
% Space (in pixels) between each parameter field column
@@ -49,30 +49,31 @@
4949
obj.Listener = event.listener(obj.UIPanel, 'SizeChanged', @obj.onResize);
5050
end
5151

52-
function [label, ctrl] = addField(obj, name, ctrl)
52+
function [label, ctrl] = addField(obj, name, type)
5353
% ADDFIELD Adds a new field label and input control
5454
% Adds a label and control element for representing Global
5555
% parameters. The input name should be identical to a parameter
56-
% fieldname. From this the label string title is derived using
57-
% exp.Parameters/title. Callback are added for the context menu and
58-
% for edits
56+
% fieldname. Type is an optional input specifying the style of
57+
% uicontrol (default 'edit'). From this the label string title is
58+
% derived using exp.Parameters/title. Callbacks are added for the
59+
% context menu and for edits
5960
%
6061
% See also ONEDIT, EXP.PARAMETERS/TITLE, EUI.PARAMEDITOR/BUILDUI
62+
if nargin < 3; type = 'edit'; end
6163
if isempty(obj.ContextMenu)
6264
obj.ContextMenu = uicontextmenu;
6365
uimenu(obj.ContextMenu, 'Label', 'Make Conditional', ...
6466
'MenuSelectedFcn', @(~,~)obj.makeConditional);
6567
end
6668
% props.BackgroundColor = 'white';
69+
props.TooltipString = obj.ParamEditor.Parameters.description(name);
6770
props.HorizontalAlignment = 'left';
6871
props.UIContextMenu = obj.ContextMenu;
6972
props.Parent = obj.UIPanel;
7073
props.Tag = name;
7174
title = obj.ParamEditor.Parameters.title(name);
7275
label = uicontrol('Style', 'text', 'String', title, props);
73-
if nargin < 3
74-
ctrl = uicontrol('Style', 'edit', props);
75-
end
76+
ctrl = uicontrol('Style', type, props);
7677
callback = @(src,~)onEdit(obj, src, name);
7778
set(ctrl, 'Callback', callback);
7879
obj.Labels = [obj.Labels; label];

+eui/ParamEditor.m

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
% obj.Listener = event.listener(parent, 'SizeChanged', @(~,~)obj.onResize);
6060
obj.Parent = uix.HBox('Parent', parent);
6161
obj.GlobalUI = eui.FieldPanel(obj.Parent, obj);
62+
if nargin == 2; obj.GlobalUI.Margin = 14; end % FIXME Add as generic input name-value pair
6263
obj.ConditionalUI = eui.ConditionPanel(obj.Parent, obj);
6364
obj.buildUI(pars);
6465
% FIXME Current hack for drawing params first time
@@ -138,9 +139,8 @@ function buildUI(obj, pars)
138139
% context menu, don't create global param field
139140
if strcmp(nm, 'randomiseConditions'); continue; end
140141
if islogical(pars.Struct.(nm{:})) % If parameter is logical, make checkbox
141-
ctrl = uicontrol('Parent', c.UIPanel, 'Style', 'checkbox', ...
142-
'Value', pars.Struct.(nm{:}), 'Tag', nm{:});
143-
addField(c, nm{:}, ctrl);
142+
[~, ctrl] = addField(c, nm{:}, 'checkbox');
143+
ctrl.Value = pars.Struct.(nm{:});
144144
else % Otherwise create the default field; a text box
145145
[~, ctrl] = addField(c, nm{:});
146146
ctrl.String = obj.paramValue2Control(pars.Struct.(nm{:}));
@@ -242,9 +242,8 @@ function globaliseParamAtCell(obj, name, row)
242242
obj.ConditionalUI.fillConditionTable;
243243
% Add new global parameter to field panel
244244
if islogical(value) % If parameter is logical, make checkbox
245-
ctrl = uicontrol('Parent', obj.GlobalUI.UIPanel, ...
246-
'Style', 'checkbox', 'Value', value);
247-
addField(obj.GlobalUI, name, ctrl);
245+
[~, ctrl] = addField(obj.GlobalUI, name, 'checkbox');
246+
ctrl.Value = value;
248247
else
249248
[~, ctrl] = addField(obj.GlobalUI, name);
250249
ctrl.String = obj.paramValue2Control(value);

tests/ParamEditorPerfTest.m

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
classdef ParamEditorPerfTest < matlab.perftest.TestCase
2+
3+
properties
4+
% Figure visibility setting before running tests
5+
FigureVisibleDefault
6+
% ParamEditor instance
7+
ParamEditor
8+
% Figure handle for ParamEditor
9+
Figure
10+
% Handle to trial conditions UI Table
11+
Table
12+
% Test parameter structure
13+
Parameters
14+
end
15+
16+
methods (TestClassSetup)
17+
function setup(testCase)
18+
% Hide figures and add teardown function to restore settings
19+
testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible');
20+
set(0,'DefaultFigureVisible','off');
21+
testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault);
22+
23+
% Loads validation data
24+
% Graph data is a cell array where each element is the graph number
25+
% (1:3) and within each element is a cell of X- and Y- axis values
26+
% respecively
27+
testCase.Parameters = exp.choiceWorldParams;
28+
29+
% Check paths file
30+
assert(endsWith(which('dat.paths'), fullfile('tests','+dat','paths.m')));
31+
% Create stand-alone panel
32+
testCase.ParamEditor = eui.ParamEditor;
33+
testCase.Figure = gcf();
34+
testCase.addTeardown(@close, testCase.Figure);
35+
assert(isa(testCase.ParamEditor, 'eui.ParamEditor'))
36+
% Find Condition Table
37+
testCase.Table = findobj(testCase.Figure, '-property', 'ColumnName');
38+
assert(isa(testCase.Table, 'matlab.ui.control.Table'), ...
39+
'Failed to find handle to condition table')
40+
end
41+
42+
end
43+
44+
methods (TestMethodSetup)
45+
function buildParams(testCase)
46+
% Re-build the parameters before each test so that changes in
47+
% previous test don't persist
48+
PE = testCase.ParamEditor;
49+
pars = exp.Parameters(testCase.Parameters);
50+
PE.buildUI(pars);
51+
% Number of global parameters: find all text labels
52+
nGlobalLabels = numel(findobj(testCase.Figure, 'Style', 'text'));
53+
nGlobalInput = numel(findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit'));
54+
% Ensure all global params have UI input and label
55+
assert(nGlobalLabels == numel(PE.Parameters.GlobalNames))
56+
assert(nGlobalInput == numel(PE.Parameters.GlobalNames))
57+
% Ensure all conditional params have column in table
58+
assert(isequal(size(testCase.Table.Data), ...
59+
[PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]))
60+
end
61+
end
62+
63+
methods (Test)
64+
function test_newParamEditor(testCase)
65+
% Test instantiate new param editor from scratch
66+
pars = exp.Parameters(testCase.Parameters);
67+
testCase.startMeasuring();
68+
PE = eui.ParamEditor(pars);
69+
testCase.stopMeasuring();
70+
close(gcf)
71+
delete(PE)
72+
end
73+
74+
function test_buildUI(testCase)
75+
% Test clear and rebuild params
76+
pars = exp.Parameters(testCase.Parameters);
77+
testCase.startMeasuring();
78+
testCase.ParamEditor.buildUI(pars);
79+
testCase.stopMeasuring();
80+
end
81+
82+
function test_makeConditional(testCase)
83+
% Make some global params trial conditions. This test checks that
84+
% the UI elements are re-rendered after making a parameter
85+
% conditional, and that the underlying Parameters object is also
86+
% affected
87+
PE = testCase.ParamEditor;
88+
% Number of global parameters: find all text labels
89+
gLabels = @()findobj(testCase.Figure, 'Style', 'text');
90+
gInputs = @()findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit');
91+
nGlobalLabels = numel(gLabels());
92+
nGlobalInputs = numel(gInputs());
93+
tableSz = size(testCase.Table.Data);
94+
95+
% Retrieve context menu function handle
96+
c = findobj(testCase.Figure, 'Text', 'Make Conditional');
97+
% Set the focused object to one of the parameter labels
98+
set(testCase.Figure, 'CurrentObject', ...
99+
findobj(testCase.Figure, 'Tag', 'rewardVolume'))
100+
101+
%%% Make conditional %%%
102+
testCase.startMeasuring();
103+
c.MenuSelectedFcn()
104+
testCase.stopMeasuring();
105+
106+
% Verify change in UI elements
107+
testCase.verifyTrue(numel(gLabels()) == nGlobalLabels-1, ...
108+
'Global parameter UI element not removed')
109+
testCase.verifyTrue(numel(gInputs()) == nGlobalInputs-1, ...
110+
'Global parameter UI element not removed')
111+
testCase.verifyTrue(size(testCase.Table.Data,2) == tableSz(2)+1, ...
112+
'Incorrect condition table')
113+
% Verify change in Parameters object for global
114+
testCase.assertTrue(numel(gLabels()) == numel(PE.Parameters.GlobalNames))
115+
% Verify change in Parameters object for conditional
116+
testCase.assertTrue(isequal(size(testCase.Table.Data), ...
117+
[PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)]))
118+
% Verify table values are correct
119+
testCase.verifyTrue(isequal(testCase.Table.Data(:,1), repmat({'3'}, ...
120+
size(testCase.Table.Data,1), 1)), 'Unexpected table values')
121+
end
122+
123+
function test_newCondition(testCase)
124+
PE = testCase.ParamEditor;
125+
tableRows = size(testCase.Table.Data, 1);
126+
127+
% Make function handle param conditional to test default value
128+
% Set the focused object to one of the parameter labels
129+
set(testCase.Figure, 'CurrentObject', ...
130+
findobj(testCase.Figure, 'Tag', 'experimentFun'))
131+
feval(pick(findobj(testCase.Figure, 'Text', 'Make Conditional'), 'MenuSelectedFcn'))
132+
133+
% Retrieve function handle for new condition
134+
fn = pick(findobj(testCase.Figure, 'String', 'New condition'), 'Callback');
135+
testCase.startMeasuring();
136+
fn()
137+
testCase.stopMeasuring();
138+
139+
% Verify change in table data
140+
testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows+1, ...
141+
'Unexpected number of trial conditions')
142+
143+
% Verify change in Parameters object for conditional
144+
testCase.assertEqual(size(testCase.Table.Data), ...
145+
[PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])
146+
end
147+
148+
function test_deleteCondition(testCase)
149+
PE = testCase.ParamEditor;
150+
tableRows = size(testCase.Table.Data, 1);
151+
% Select some cells to delete
152+
event.Indices = [(1:5)' ones(5,1)];
153+
selection_fn = testCase.Table.CellSelectionCallback;
154+
selection_fn([],event)
155+
156+
% Retrieve function handle for delete condition
157+
callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback');
158+
testCase.startMeasuring();
159+
callback_fn()
160+
testCase.stopMeasuring();
161+
162+
% Verify change in table data
163+
testCase.verifyEqual(size(testCase.Table.Data, 1), tableRows-5, ...
164+
'Unexpected number of trial conditions')
165+
166+
% Verify change in Parameters object for conditional
167+
testCase.assertEqual(size(testCase.Table.Data), ...
168+
[PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])
169+
end
170+
171+
function test_globaliseParam(testCase)
172+
PE = testCase.ParamEditor;
173+
tableCols = size(testCase.Table.Data, 2);
174+
% Globalize one param
175+
event.Indices = [1, 2];
176+
selection_fn = testCase.Table.CellSelectionCallback;
177+
selection_fn([],event)
178+
179+
% Retrieve function handle for new condition
180+
callback_fn = pick(findobj(testCase.Figure, 'String', 'Globalise parameter'), 'Callback');
181+
testCase.startMeasuring();
182+
callback_fn()
183+
testCase.stopMeasuring();
184+
185+
% Verify change in table data
186+
testCase.verifyEqual(size(testCase.Table.Data,2), tableCols-1, ...
187+
'Unexpected number of conditional parameters')
188+
% Verify change in Parameters object for conditional
189+
testCase.verifyEqual(size(testCase.Table.Data), ...
190+
[PE.Parameters.numTrialConditions, numel(PE.Parameters.TrialSpecificNames)])
191+
end
192+
193+
function test_paramEdits(testCase)
194+
% Test basic edits to Global UI controls and Condition table
195+
PE = testCase.ParamEditor;
196+
197+
% Retreive all global parameters labels and input controls
198+
gLabels = findobj(testCase.Figure, 'Style', 'text');
199+
gInputs = findobj(testCase.Figure, 'Style', 'checkbox', '-or', 'Style', 'edit');
200+
201+
% Test editing global param, 'edit' UI
202+
idx = find(strcmp({gInputs.Style}, 'edit'), 1);
203+
% Change string
204+
gInputs(idx).String = '666';
205+
% Trigger callback
206+
callback_fcn = gInputs(idx).Callback;
207+
testCase.startMeasuring();
208+
callback_fcn(gInputs(idx));
209+
testCase.stopMeasuring();
210+
211+
% Verify change in ui string
212+
testCase.verifyEqual(gInputs(idx).String, '666')
213+
% Verify change in label color
214+
testCase.verifyEqual(gLabels(idx).ForegroundColor, [1 0 0], ...
215+
'Unexpected label colour')
216+
% Verify change in underlying param struct
217+
par = strcmpi(PE.Parameters.GlobalNames, strrep(gLabels(idx).String, ' ', ''));
218+
testCase.verifyEqual(PE.Parameters.Struct.(PE.Parameters.GlobalNames{par}), 666, ...
219+
'UI edit failed to update parameters struct')
220+
221+
% Test edits to conditions table
222+
callback_fcn = testCase.Table.CellEditCallback;
223+
event.Indices = [1, 1];
224+
event.NewData = '0,5';
225+
testCase.startMeasuring();
226+
callback_fcn(testCase.Table, event)
227+
testCase.stopMeasuring();
228+
229+
% Verify change to table value
230+
testCase.verifyEqual(testCase.Table.Data{1,1}, '0, 5', ...
231+
'Unexpected table data')
232+
% Verify change in underlying param struct
233+
value = PE.Parameters.Struct.(PE.Parameters.TrialSpecificNames{1});
234+
testCase.verifyEqual(value(:,1), [0;5], ...
235+
'Table UI failed to update parameters struct')
236+
end
237+
238+
end
239+
240+
end

tests/ParamEditorTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
function setup(testCase)
2424
% Hide figures and add teardown function to restore settings
2525
testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible');
26-
% set(0,'DefaultFigureVisible','off');
26+
set(0,'DefaultFigureVisible','off');
2727
testCase.addTeardown(@set, 0, 'DefaultFigureVisible', testCase.FigureVisibleDefault);
2828

2929
% Loads validation data
@@ -209,7 +209,7 @@ function test_deleteCondition(testCase)
209209
selection_fn = testCase.Table.CellSelectionCallback;
210210
selection_fn([],event)
211211

212-
% Retrieve function handle for new condition
212+
% Retrieve function handle for delete condition
213213
callback_fn = pick(findobj(testCase.Figure, 'String', 'Delete condition'), 'Callback');
214214
testCase.verifyWarningFree(callback_fn, 'Warning encountered deleting trial conditions')
215215

0 commit comments

Comments
 (0)