Skip to content

Commit b85f8f8

Browse files
author
Jai Bhagat
authored
Merge pull request #119 from cortex-lab/paramUI
Param ui into dev
2 parents 228a016 + 4c9b67f commit b85f8f8

21 files changed

+2330
-467
lines changed

+eui/ConditionPanel.m

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
classdef ConditionPanel < handle
2+
%CONDITIONPANEL Deals with formatting trial conditions UI table
3+
% Designed to be an element of the EUI.PARAMEDITOR class that manages
4+
% the UI elements associated with all Conditional parameters.
5+
% TODO Add sort by column
6+
% TODO Add set condition idx
7+
8+
properties
9+
% Handle to UI Table that represents trial conditions
10+
ConditionTable
11+
% Minimum UI Panel width allowed. See also EUI.PARAMEDITOR/ONRESIZE
12+
MinWidth = 80
13+
% Handle to parent UI container
14+
UIPanel
15+
% Handle to UI container for buttons
16+
ButtonPanel
17+
% Handles to context menu items
18+
ContextMenus
19+
end
20+
21+
properties (Access = protected)
22+
% Handle to EUI.PARAMEDITOR object
23+
ParamEditor
24+
% UIControl button for adding a new trial condition (row) to the table
25+
NewConditionButton
26+
% UIControl button for deleting trial conditions (rows) from the table
27+
DeleteConditionButton
28+
% UIControl button for making conditional parameter (column) global
29+
MakeGlobalButton
30+
% UIControl button for setting multiple table cells at once
31+
SetValuesButton
32+
% Indicies of selected table cells as array [row, column;...] of each
33+
% selected cell
34+
SelectedCells
35+
end
36+
37+
methods
38+
function obj = ConditionPanel(f, ParamEditor, varargin)
39+
% FIELDPANEL Panel UI for Conditional parameters
40+
% Input f may be a figure or other UI container object
41+
% ParamEditor is a handle to an eui.ParamEditor object.
42+
%
43+
% See also EUI.PARAMEDITOR, EUI.FIELDPANEL
44+
obj.ParamEditor = ParamEditor;
45+
obj.UIPanel = uix.VBox('Parent', f);
46+
% Create a child menu for the uiContextMenus
47+
c = uicontextmenu;
48+
obj.UIPanel.UIContextMenu = c;
49+
obj.ContextMenus = uimenu(c, 'Label', 'Make Global', ...
50+
'MenuSelectedFcn', @(~,~)obj.makeGlobal, 'Enable', 'off');
51+
fcn = @(s,~)obj.ParamEditor.setRandomized(~strcmp(s.Checked, 'on'));
52+
obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ...
53+
'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button');
54+
obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ...
55+
'MenuSelectedFcn', @(~,~)disp('feature not yet implemented'), ...
56+
'Tag', 'sort by', 'Enable', 'off'); % TODO Implement sort by column
57+
% Create condition table
58+
p = uix.Panel('Parent', obj.UIPanel, 'BorderType', 'none');
59+
obj.ConditionTable = uitable('Parent', p,...
60+
'FontName', 'Consolas',...
61+
'RowName', [],...
62+
'RearrangeableColumns', 'on',...
63+
'Units', 'normalized',...
64+
'Position',[0 0 1 1],...
65+
'UIContextMenu', c,...
66+
'CellEditCallback', @obj.onEdit,...
67+
'CellSelectionCallback', @obj.onSelect);
68+
% Create button panel to hold condition control buttons
69+
obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel);
70+
% Define some common properties
71+
props.Style = 'pushbutton';
72+
props.Units = 'normalized';
73+
props.Parent = obj.ButtonPanel;
74+
% Create out four buttons
75+
obj.NewConditionButton = uicontrol(props,...
76+
'String', 'New condition',...
77+
'TooltipString', 'Add a new condition',...
78+
'Callback', @(~, ~) obj.newCondition());
79+
obj.DeleteConditionButton = uicontrol(props,...
80+
'String', 'Delete condition',...
81+
'TooltipString', 'Delete the selected condition',...
82+
'Enable', 'off',...
83+
'Callback', @(~, ~) obj.deleteSelectedConditions());
84+
obj.MakeGlobalButton = uicontrol(props,...
85+
'String', 'Globalise parameter',...
86+
'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'...
87+
'This will move it to the global parameters section']),...
88+
'Enable', 'off',...
89+
'Callback', @(~, ~) obj.makeGlobal());
90+
obj.SetValuesButton = uicontrol(props,...
91+
'String', 'Set values',...
92+
'TooltipString', 'Set selected values to specified value, range or function',...
93+
'Enable', 'off',...
94+
'Callback', @(~, ~) obj.setSelectedValues());
95+
obj.ButtonPanel.Widths = [-1 -1 -1 -1];
96+
obj.UIPanel.Heights = [-1 25];
97+
end
98+
99+
function onEdit(obj, src, eventData)
100+
% ONEDIT Callback for edits to condition table
101+
% Updates the underlying parameter struct, changes the UI table
102+
% data. The src object should be the UI Table that has been edited,
103+
% and eventData contains the table indices of the edited cell.
104+
%
105+
% See also FILLCONDITIONTABLE, EUI.PARAMEDITOR/UPDATE
106+
row = eventData.Indices(1);
107+
col = eventData.Indices(2);
108+
assert(all(cellfun(@strcmpi, strrep(obj.ConditionTable.ColumnName, ' ', ''), ...
109+
obj.ParamEditor.Parameters.TrialSpecificNames)), 'Unexpected condition names')
110+
paramName = obj.ParamEditor.Parameters.TrialSpecificNames{col};
111+
newValue = obj.ParamEditor.update(paramName, eventData.NewData, row);
112+
reformed = obj.ParamEditor.paramValue2Control(newValue);
113+
% If successful update the cell with default formatting
114+
data = get(src, 'Data');
115+
if iscell(reformed)
116+
% The reformed data type is a cell, this should be a one element
117+
% wrapping cell
118+
if numel(reformed) == 1
119+
reformed = reformed{1};
120+
else
121+
error('Cannot handle data reformatted data type');
122+
end
123+
end
124+
data{row,col} = reformed;
125+
set(src, 'Data', data);
126+
end
127+
128+
function clear(obj)
129+
% CLEAR Clear all table data
130+
% Clears all trial condition data from UI Table
131+
%
132+
% See also EUI.PARAMEDITOR/BUILDUI, EUI.PARAMEDITOR/CLEAR
133+
set(obj.ConditionTable, 'ColumnName', [], ...
134+
'Data', [], 'ColumnEditable', false);
135+
end
136+
137+
function delete(obj)
138+
% DELETE Deletes the UI container
139+
% Called when this object or its parent ParamEditor is deleted
140+
% See also CLEAR
141+
delete(obj.UIPanel);
142+
end
143+
144+
function onSelect(obj, ~, eventData)
145+
% ONSELECT Callback for when table cells are (de-)selected
146+
% If at least one cell is selected, ensure buttons and menu items
147+
% are enabled, otherwise disable them.
148+
if nargin > 2; obj.SelectedCells = eventData.Indices; end
149+
controls = ...
150+
[obj.MakeGlobalButton, ...
151+
obj.DeleteConditionButton, ...
152+
obj.SetValuesButton, ...
153+
obj.ContextMenus([1,3])];
154+
set(controls, 'Enable', iff(size(obj.SelectedCells, 1) > 0, 'on', 'off'));
155+
end
156+
157+
function makeGlobal(obj)
158+
% MAKEGLOBAL Make condition parameter (table column) global
159+
% Find all selected columns are turn into global parameters, using
160+
% the value of the first selected cell as the global parameter
161+
% value.
162+
%
163+
% See also eui.ParamEditor/globaliseParamAtCell
164+
if isempty(obj.SelectedCells)
165+
disp('nothing selected')
166+
return
167+
end
168+
PE = obj.ParamEditor;
169+
[cols, iu] = unique(obj.SelectedCells(:,2));
170+
names = PE.Parameters.TrialSpecificNames(cols);
171+
rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols
172+
cellfun(@PE.globaliseParamAtCell, names, rows);
173+
% If only numRepeats remains, globalise it
174+
if isequal(PE.Parameters.TrialSpecificNames, {'numRepeats'})
175+
PE.Parameters.Struct.numRepeats(1,1) = sum(PE.Parameters.Struct.numRepeats);
176+
PE.globaliseParamAtCell('numRepeats', 1)
177+
end
178+
end
179+
180+
function deleteSelectedConditions(obj)
181+
%DELETESELECTEDCONDITIONS Removes the selected conditions from table
182+
% The callback for the 'Delete condition' button. This removes the
183+
% selected conditions from the table and if less than two conditions
184+
% remain, globalizes them.
185+
%
186+
% See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS
187+
rows = unique(obj.SelectedCells(:,1));
188+
names = obj.ConditionTable.ColumnName;
189+
numConditions = size(obj.ConditionTable.Data,1);
190+
% If the number of remaining conditions is 1 or less...
191+
if numConditions-length(rows) <= 1
192+
remainingIdx = find(all(1:numConditions~=rows,1));
193+
if isempty(remainingIdx); remainingIdx = 1; end
194+
% change selected cells to be all fields (except numRepeats which
195+
% is assumed to always be the last column)
196+
obj.SelectedCells =[ones(length(names),1)*remainingIdx, (1:length(names))'];
197+
%... globalize them
198+
obj.makeGlobal;
199+
else % Otherwise delete the selected conditions as usual
200+
obj.ParamEditor.Parameters.removeConditions(rows);
201+
notify(obj.ParamEditor, 'Changed')
202+
end
203+
% Refresh the table of conditions
204+
obj.fillConditionTable();
205+
end
206+
207+
function setSelectedValues(obj)
208+
% SETSELECTEDVALUES Set multiple fields in conditional table at once
209+
% Generates an input dialog for setting multiple trial conditions at
210+
% once. Also allows the use of function handles for more complex
211+
% values.
212+
%
213+
% Examples:
214+
% (1:10:100) % Sets selected rows to [1 11 21 31 41 51 61 71 81 91]
215+
% @(~)randi(100) % Assigned random integer to each selected row
216+
% @(a)a*50 % Multiplies each condition value by 50
217+
% false % Sets all selected rows to false
218+
%
219+
% See also SETNEWVALS, ONEDIT
220+
cols = obj.SelectedCells(:,2); % selected columns
221+
uCol = unique(obj.SelectedCells(:,2));
222+
rows = obj.SelectedCells(:,1); % selected rows
223+
% get current values of selected cells
224+
currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0);
225+
names = obj.ConditionTable.ColumnName(uCol); % selected column names
226+
promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],...
227+
names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows
228+
defaultans = cellfun(@(c) c(1), currVals);
229+
answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input
230+
if isempty(answer) % if user presses cancel
231+
return
232+
end
233+
% set values for each column
234+
cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0);
235+
function newVals = setNewVals(userIn, currVals, paramName)
236+
% check array orientation
237+
currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals);
238+
if strStartsWith(userIn,'@') % anon function
239+
func_h = str2func(userIn);
240+
% apply function to each cell
241+
currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char
242+
newVals = cellfun(func_h, currVals, 'UniformOutput', 0);
243+
elseif any(userIn==':') % array syntax
244+
arr = eval(userIn);
245+
newVals = num2cell(arr); % convert to cell array
246+
elseif any(userIn==','|userIn==';') % 2D arrays
247+
C = strsplit(userIn, ';');
248+
newVals = cellfun(@(c)textscan(c, '%f',...
249+
'ReturnOnError', false,...
250+
'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),...
251+
C);
252+
else % single value to copy across all cells
253+
userIn = str2double(userIn);
254+
newVals = num2cell(ones(size(currVals))*userIn);
255+
end
256+
257+
if length(newVals)>length(currVals) % too many new values
258+
newVals = newVals(1:length(currVals)); % truncate new array
259+
elseif length(newVals)<length(currVals) % too few new values
260+
% populate as many cells as possible
261+
newVals = [newVals ...
262+
cellfun(@(a)obj.ParamEditor.controlValue2Param(2,a),...
263+
currVals(length(newVals)+1:end),'UniformOutput',0)];
264+
end
265+
ic = strcmp(obj.ConditionTable.ColumnName, paramName); % find edited param names
266+
% update param struct
267+
obj.ParamEditor.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals);
268+
% update condtion table with strings
269+
obj.ConditionTable.Data(rows(cols==find(ic)),ic)...
270+
= cellfun(@(a)ui.ParamEditor.paramValue2Control(a), newVals', 'UniformOutput', 0);
271+
end
272+
notify(obj.ParamEditor, 'Changed');
273+
end
274+
275+
function fillConditionTable(obj)
276+
% FILLCONDITIONTABLE Build the condition table
277+
% Populates the UI Table with trial specific parameters, where each
278+
% row is a trial condition (that is, a parameter column) and each
279+
% column is a different trial specific parameter
280+
P = obj.ParamEditor.Parameters;
281+
titles = P.title(P.TrialSpecificNames);
282+
[~, trialParams] = P.assortForExperiment;
283+
if isempty(titles)
284+
obj.ButtonPanel.Visible = 'off';
285+
obj.UIPanel.Visible = 'off';
286+
obj.ParamEditor.Parent.Widths = [-1, 1];
287+
else
288+
obj.ButtonPanel.Visible = 'on';
289+
obj.UIPanel.Visible = 'on';
290+
obj.ParamEditor.Parent.Widths = [-1, -1];
291+
end
292+
data = reshape(struct2cell(trialParams), numel(titles), [])';
293+
data = mapToCell(@(e) obj.ParamEditor.paramValue2Control(e), data);
294+
set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,...
295+
'ColumnEditable', true(1, numel(titles)));
296+
end
297+
298+
function newCondition(obj)
299+
% Adds a new trial condition (row) to the ConditionTable
300+
% Adds new row and populates it with sensible 'default' values.
301+
% These are mostly zeros or empty values.
302+
% See also eui.ParamEditor/addEmptyConditionToParam
303+
PE = obj.ParamEditor;
304+
cellfun(@PE.addEmptyConditionToParam, ...
305+
PE.Parameters.TrialSpecificNames);
306+
obj.fillConditionTable();
307+
end
308+
309+
end
310+
311+
end

0 commit comments

Comments
 (0)