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