1 | function out = tableGUI(varargin) |
---|
2 | % TABLEGUI - Spreadsheet like display and edition of a generic 2D array. By generic it is |
---|
3 | % mean that the array can be a numeric MxN matrix or a MxN cell array (with mixed |
---|
4 | % number and text strings). This function imitates the table cells with edit boxes |
---|
5 | % which may become slow if the number of elements required is large. However, it |
---|
6 | % works fast for small matrices. If the default number of rows is exceeded a vertical |
---|
7 | % slider is created. The slider works by changing the position of the table elements, |
---|
8 | % which again may become slow if the default number of visible rows is large. Otherwise |
---|
9 | % it works pretty fast. |
---|
10 | % |
---|
11 | % USAGE: |
---|
12 | % OUT = TABLEGUI(varargin) |
---|
13 | % |
---|
14 | % Inputs are in property/value pairs. All properties are strings but the values are |
---|
15 | % of different types depending on the case. |
---|
16 | % |
---|
17 | % PROPERTY VALUE TYPE |
---|
18 | % |
---|
19 | % 'array' It can be either an numeric matrix or a MxN numeric or cell array |
---|
20 | % cell array. |
---|
21 | % 'NumRows' Total number of rows to create. Use this when the integer |
---|
22 | % 'array' option is not used, or when you want to |
---|
23 | % create extra empty rows. |
---|
24 | % 'NumCol' Total number of columns to create. Use this when integer |
---|
25 | % the 'array' option is not used, but ignored if it was. |
---|
26 | % 'MAX_ROWS' Number of visible rows. If NumRows > MAX_ROWS integer |
---|
27 | % a vertical slider is created (DEF = 10) |
---|
28 | % 'RowHeight' editboxes height in pixels (DEF = 20) integer |
---|
29 | % 'ColWidth' editboxes width in pixels (DEF = 60) scalar or row vector |
---|
30 | % If a vector is transmited, it must contains NumCol |
---|
31 | % elements which set individual column widths. |
---|
32 | % 'bd_size' space between editboxes width in pixels (DEF = 0) integer |
---|
33 | % 'HorAlin' editboxes 'HorizontalAlignment' property. It can string |
---|
34 | % be either 'left', 'center' or 'right' (DEF = 'center') |
---|
35 | % 'HdrButtons' create a first row of buttons to hold column string - either '' or 'y' |
---|
36 | % names. Give an empty string ('') if you don't want |
---|
37 | % column names (DEF = 'Y'). |
---|
38 | % 'ColNames' Cell array of strings for column names. If not 1xN cell array of strings |
---|
39 | % provided the columns are named 'A', 'B', ... |
---|
40 | % 'RowNumbers' Add a first column with row numbers. Note that this string - either '' or 'y' |
---|
41 | % column is set to 'inactive' and its not transmited |
---|
42 | % on the output (DEF = ''). |
---|
43 | % 'checks' If = 'Y' it creates a vertical line of checkboxes string - either '' or 'y' |
---|
44 | % This affects what is send as output. Only rows that |
---|
45 | % have it's checkbox checked will be returned. |
---|
46 | % 'FigName' Name that appears in the title bar (DEF = 'Table'). string |
---|
47 | % 'position' Screen location to be used in the call to MOVEGUI string |
---|
48 | % See doc of that function for valid position |
---|
49 | % strings (DEF = 'east'). |
---|
50 | % 'modal' By default the window works in MODAL mode. Give an string - either '' or 'y' |
---|
51 | % empty string ('') if you don't want it to be MODAL. |
---|
52 | % In this later case the output OUT, if requested, will |
---|
53 | % contain the figure handle but see more about this below. |
---|
54 | % |
---|
55 | % OUT - the output - contains different things depending whether or not the |
---|
56 | % figure works in MODAL mode. If yes, OUT is a MxN cell array with the |
---|
57 | % elements retrived from the contents of the edit boxes. Otherwise, OUT |
---|
58 | % will contain the figure's handle. This handle has the 'UserData' property |
---|
59 | % filled with a structure (called hand) which contains the handles of all |
---|
60 | % uicontrols. Use this option if you want to interact with the TABLEGUI |
---|
61 | % figure inside your own code. |
---|
62 | % |
---|
63 | % Examples: |
---|
64 | % - Display a 12x6 numeric matrix with two extra blank rows appended to the end |
---|
65 | % out = tableGUI('array',rand(12,6),'ColNames',{'1' '2' '3' '4' '5' '6'},'NumRows',14); |
---|
66 | % |
---|
67 | % - Create a cell array with the first column filled with the row number and use |
---|
68 | % columns with different widths. Create also check boxes. |
---|
69 | % zz=cell(4,5); zz(:,1) = num2cell(1:4)'; |
---|
70 | % out = tableGUI('array',zz,'ColNames',{'N','A','B','C','D'},'ColWidth',[20 60 60 60 60],'checks','y'); |
---|
71 | % |
---|
72 | % - Create a similar table as in the previous example but with the row numbers option. |
---|
73 | % out = tableGUI('array',cell(4,5),'RowNumbers','y','checks','y'); |
---|
74 | % |
---|
75 | % - Display the Control Points of the Image Processing Toolbox example |
---|
76 | % "Registering an Aerial Photo to an Orthophoto" |
---|
77 | % load westconcordpoints % load some points that were already picked |
---|
78 | % gcp = [base_points input_points]; |
---|
79 | % out = tableGUI('array',gcp,'RowNumbers','y','ColNames',{'Base Points - X','Base Points - Y',... |
---|
80 | % 'Input Points - X','Input Points - Y'},'ColWidth',110,'FigName','GCP Table'); |
---|
81 | % |
---|
82 | % - Create an empty 12x6 empty table |
---|
83 | % out = tableGUI; |
---|
84 | % |
---|
85 | % Acknowledgment |
---|
86 | % This function uses the parse_pv_pairs of John d'Errico |
---|
87 | % |
---|
88 | % AUTHOR |
---|
89 | % Joaquim Luis (jluis@ualg.pt) 17-Feb-2006 |
---|
90 | |
---|
91 | hand.NumRows = 12; hand.NumCol = 6; |
---|
92 | hand.MAX_ROWS = 10; hand.left_marg = 10; |
---|
93 | hand.RowHeight = 20; hand.bd_size = 0; |
---|
94 | hand.checks = ''; hand.HdrButtons = 'Y'; |
---|
95 | hand.HorAlin = 'center'; hand.FigName = 'Table'; |
---|
96 | hand.array = cell(hand.NumRows,hand.NumCol); |
---|
97 | hand.modal = 'y'; hand.position = 'east'; |
---|
98 | hand.RowNumbers = ''; d_col = 0; |
---|
99 | |
---|
100 | if (nargin == 0) % Demo |
---|
101 | hand.ColNames = {'A' 'B' 'C' 'D' 'E' 'F'}; |
---|
102 | hand.ColWidth = [50 80 80 80 80 50]; |
---|
103 | else |
---|
104 | hand.ColNames = ''; hand.ColWidth = []; |
---|
105 | hand.array = []; def_NumRows = hand.NumRows; |
---|
106 | hand = parse_pv_pairs(hand,varargin); |
---|
107 | if (~isempty(hand.array)) |
---|
108 | if (numel(hand.array) == 1 && numel(hand.array{1}) ~= 1) |
---|
109 | error('The "array" argument must be a MxN cell array and not a {MxN} cell') |
---|
110 | end |
---|
111 | [NumRows,hand.NumCol] = size(hand.array); |
---|
112 | if (~iscell(hand.array)) % We need as a cell array to be more general |
---|
113 | hand.array = num2cell(hand.array); |
---|
114 | end |
---|
115 | if (NumRows < hand.NumRows && hand.NumRows ~= def_NumRows) % Extra rows requested |
---|
116 | hand.array = [hand.array; cell(hand.NumRows-NumRows,hand.NumCol)]; |
---|
117 | else |
---|
118 | hand.NumRows = NumRows; |
---|
119 | end |
---|
120 | if (hand.NumRows < hand.MAX_ROWS), hand.MAX_ROWS = hand.NumRows; end |
---|
121 | |
---|
122 | else % 'array' not transmited |
---|
123 | hand.array = cell(hand.NumRows,hand.NumCol); |
---|
124 | end |
---|
125 | |
---|
126 | if (isempty(hand.ColNames) && ~isempty(hand.HdrButtons)) % By default columns are labeled 'A','B',... |
---|
127 | hand.ColNames = cell(1,hand.NumCol); |
---|
128 | for (i = 1:hand.NumCol), hand.ColNames{1,i} = char(i+64); end |
---|
129 | end |
---|
130 | if (size(hand.array,2) > size(hand.ColNames,2)) |
---|
131 | error('"ColNames" argument has less elements than the number of columns is "array"') |
---|
132 | end |
---|
133 | if (isempty(hand.ColWidth)) % Use default value for button width |
---|
134 | hand.ColWidth = repmat(60,1,hand.NumCol); |
---|
135 | elseif (numel(hand.ColWidth) == 1) % 'ColWidth' was a scalar |
---|
136 | hand.ColWidth = repmat(hand.ColWidth,1,hand.NumCol); |
---|
137 | end |
---|
138 | |
---|
139 | if (~isempty(hand.RowNumbers)) % Row numbering was requested |
---|
140 | hand.ColWidth = [35 hand.ColWidth]; |
---|
141 | hand.NumCol = hand.NumCol + 1; |
---|
142 | hand.array = [cell(hand.NumRows,1) hand.array]; |
---|
143 | d_col = 1; |
---|
144 | end |
---|
145 | end |
---|
146 | |
---|
147 | arr_pos_xi = [hand.left_marg + [0 (cumsum(hand.ColWidth+hand.bd_size))]]; |
---|
148 | arr_pos_xi(end) = []; % We don't want the last element |
---|
149 | arr_pos_xw = hand.ColWidth; |
---|
150 | |
---|
151 | % ---------------- Create the figure ---------------------------------- |
---|
152 | fig_height = min(hand.NumRows,hand.MAX_ROWS) * (hand.RowHeight+hand.bd_size); |
---|
153 | if (~isempty(hand.HdrButtons)), fig_height = fig_height + 22; end % Make room for header buttons |
---|
154 | if (~isempty(hand.modal)), fig_height = fig_height + 30; end % Make room for OK,Cancel buttons |
---|
155 | pos = [5 75 sum(arr_pos_xw)+hand.left_marg+(hand.NumCol-1)*hand.bd_size+15 fig_height]; % The 15 is for the slider |
---|
156 | if (~isempty(hand.checks)), pos(3) = pos(3) + 15; end % Account for checkboxes size |
---|
157 | hand.hFig = figure('unit','pixels','NumberTitle','off','Menubar','none','resize','on','position', ... |
---|
158 | pos,'Name',hand.FigName,'Resize','off','Visible','off'); |
---|
159 | movegui(hand.hFig,hand.position) |
---|
160 | |
---|
161 | hand.arr_pos_y = (fig_height-hand.RowHeight-hand.bd_size - (0:hand.NumRows-1)*(hand.RowHeight+hand.bd_size))'; |
---|
162 | if (~isempty(hand.HdrButtons)), hand.arr_pos_y = hand.arr_pos_y - 22; end |
---|
163 | |
---|
164 | if (~isempty(hand.checks)) % Create the checkboxes uicontrols |
---|
165 | arr_pos_xi = arr_pos_xi + 15; % Make room for them |
---|
166 | hand.hChecks = zeros(hand.NumRows,1); |
---|
167 | hand.Checks_pos_orig = [ones(hand.NumRows,1)*7 (hand.arr_pos_y+3) ones(hand.NumRows,1)*15 ones(hand.NumRows,1)*15]; |
---|
168 | end |
---|
169 | hand.hEdits = zeros(hand.NumRows,hand.NumCol); |
---|
170 | hand.Edits_pos_orig = cell(hand.NumRows,hand.NumCol); |
---|
171 | |
---|
172 | % ---------------- Create the edit uicontrols --------------------------- |
---|
173 | for (i = 1:hand.NumRows) |
---|
174 | if (~isempty(hand.checks)) |
---|
175 | hand.hChecks(i) = uicontrol('Style','checkbox','unit','pixels','position', ... |
---|
176 | hand.Checks_pos_orig(i,:),'Value',1); |
---|
177 | end |
---|
178 | for (j = 1:hand.NumCol) |
---|
179 | hand.Edits_pos_orig{i,j} = [arr_pos_xi(j) hand.arr_pos_y(i) arr_pos_xw(j) 20]; |
---|
180 | hand.hEdits(i,j) = uicontrol('Style','edit','unit','pixels','backgroundcolor','w','position', ... |
---|
181 | [arr_pos_xi(j) hand.arr_pos_y(i) arr_pos_xw(j) 20],'String',hand.array{i,j},... |
---|
182 | 'HorizontalAlignment',hand.HorAlin); |
---|
183 | end |
---|
184 | if (~isempty(hand.RowNumbers)) |
---|
185 | set(hand.hEdits(i,1),'String',i,'Enable','inactive','Background',[200 200 145]/255,'UserData',i) |
---|
186 | else |
---|
187 | set(hand.hEdits(i,1),'UserData',i) |
---|
188 | end |
---|
189 | end |
---|
190 | if (~isempty(hand.HdrButtons)) % Create the header pushbutton uicontrols |
---|
191 | for (j = 1:hand.NumCol-d_col) % The d_col is to account for an eventual 'RowNumbers' option |
---|
192 | uicontrol('Style','pushbutton','unit','pixels','Enable','inactive','position', ... |
---|
193 | [arr_pos_xi(j+d_col) hand.arr_pos_y(1)+hand.RowHeight hand.ColWidth(j+d_col) 20],'String',hand.ColNames{j}) |
---|
194 | end |
---|
195 | end |
---|
196 | |
---|
197 | % ---------------- See if we need a slider --------------------------- |
---|
198 | pos_t = get(hand.hEdits(1,hand.NumCol),'pos'); % Get top right edit position |
---|
199 | pos_b = get(hand.hEdits(hand.MAX_ROWS,1),'pos'); % Get last visible edit position |
---|
200 | if (hand.NumRows > hand.MAX_ROWS) |
---|
201 | set(hand.hEdits(hand.MAX_ROWS+1:hand.NumRows,1:hand.NumCol),'Visible','off') % Hide those who are out of view |
---|
202 | if (~isempty(hand.checks)) |
---|
203 | set(hand.hChecks(hand.MAX_ROWS+1:hand.NumRows),'Visible','off') |
---|
204 | end |
---|
205 | pos = [pos_t(1)+pos_t(3) pos_b(2) 15 pos_t(2)+pos_t(4)-pos_b(2)]; |
---|
206 | sld_step = 1 / (hand.NumRows-1); |
---|
207 | sld_step(2) = 5 * sld_step(1); |
---|
208 | hand.hSlid = uicontrol('style','slider','units','pixels','position',pos,... |
---|
209 | 'min',1,'max',hand.NumRows,'Value',hand.NumRows,'SliderStep',sld_step); |
---|
210 | set(hand.hSlid,'callback',{@slider_Callback,hand}) |
---|
211 | set(hand.hSlid,'UserData',hand.NumRows) % Store current value |
---|
212 | end |
---|
213 | |
---|
214 | % ---------------- See if the window is MODAL --------------------------- |
---|
215 | if (~isempty(hand.modal)) |
---|
216 | uicontrol('Style','pushbutton','unit','pixels','String','OK','position',... |
---|
217 | [pos_t(1)+pos_t(3)-110 5 40 20],'FontName','Helvetica','FontSize',9,... |
---|
218 | 'callback','uiresume','tag','OK'); |
---|
219 | uicontrol('Style','pushbutton','unit','pixels','String','Cancel','position', ... |
---|
220 | [pos_t(1)+pos_t(3)-60 5 60 20],'FontName','Helvetica','FontSize',9,... |
---|
221 | 'callback','uiresume','tag','cancel'); |
---|
222 | uiwait(hand.hFig) % It also sets the Figure's visibility 'on' |
---|
223 | but = gco; |
---|
224 | if strcmp(get(but,'tag'),'OK') |
---|
225 | out = reshape(get(hand.hEdits,'String'),hand.NumRows,hand.NumCol); |
---|
226 | if (~isempty(hand.checks)) |
---|
227 | unchecked = (cell2mat(get(hand.hChecks,'Value')) == 0); |
---|
228 | out(unchecked,:) = []; % Remove unchecked rows |
---|
229 | end |
---|
230 | if (~isempty(hand.RowNumbers)) % Do not output the row numbers |
---|
231 | out = out(:,2:end); |
---|
232 | end |
---|
233 | delete(hand.hFig) |
---|
234 | elseif strcmp(get(but,'tag'),'cancel') |
---|
235 | out = []; delete(hand.hFig) |
---|
236 | else % Figure was killed |
---|
237 | out = []; |
---|
238 | end |
---|
239 | else |
---|
240 | set(hand.hFig,'Visible','on','UserData',hand) |
---|
241 | if (nargout), out = hand.hFig; end |
---|
242 | end |
---|
243 | |
---|
244 | % --------------------------------------------------------------------------- |
---|
245 | function slider_Callback(obj,event,hand) |
---|
246 | |
---|
247 | val = round(get(hand.hSlid,'Value')); |
---|
248 | old_val = get(hand.hSlid,'UserData'); |
---|
249 | ds = val - old_val; |
---|
250 | |
---|
251 | if (ds < 0) % Slider moved down |
---|
252 | n = hand.NumRows - val + 1; d_col = hand.NumRows - val; |
---|
253 | if (n+hand.MAX_ROWS-1 > hand.NumRows) % Case we jumped into the midle zone |
---|
254 | adj = (n+hand.MAX_ROWS-1 - hand.NumRows); |
---|
255 | n = n - adj; d_col = d_col - adj; |
---|
256 | end |
---|
257 | for (i = n:min(n+hand.MAX_ROWS-1,hand.NumRows)) % Update positions |
---|
258 | for (j = 1:hand.NumCol) |
---|
259 | pos = hand.Edits_pos_orig{i,j}; |
---|
260 | set(hand.hEdits(i,j),'pos',[pos(1) hand.arr_pos_y(i-d_col) pos(3:4)],'Visible','on') |
---|
261 | end |
---|
262 | if (~isempty(hand.checks)) % If we have checkboxes |
---|
263 | pos = hand.Checks_pos_orig(i,:); |
---|
264 | set(hand.hChecks(i),'pos',[pos(1) hand.arr_pos_y(i-d_col)+3 pos(3:4)],'Visible','on') |
---|
265 | end |
---|
266 | end |
---|
267 | if (i == get(hand.hEdits(hand.NumRows,1),'UserData')) % Bottom reached. Jump to there |
---|
268 | val = 1; set(hand.hSlid,'Value',val) % This also avoids useless UIs repositioning |
---|
269 | end |
---|
270 | elseif (ds > 0) % Slider moved up |
---|
271 | n = hand.NumRows - val + 1; k = hand.MAX_ROWS; |
---|
272 | if (n < hand.MAX_ROWS) % Case we jumped into the midle zone |
---|
273 | adj = (hand.MAX_ROWS - n - 0); |
---|
274 | n = n + adj; |
---|
275 | end |
---|
276 | for (i = n:-1:max(n-hand.MAX_ROWS+1,1)) % Update positions |
---|
277 | for (j = 1:hand.NumCol) |
---|
278 | pos = hand.Edits_pos_orig{i,j}; |
---|
279 | set(hand.hEdits(i,j),'pos',[pos(1) hand.arr_pos_y(k) pos(3:4)],'Visible','on') |
---|
280 | end |
---|
281 | if (~isempty(hand.checks)) % If we have checkboxes |
---|
282 | pos = hand.Checks_pos_orig(i,:); |
---|
283 | set(hand.hChecks(i),'pos',[pos(1) hand.arr_pos_y(k)+3 pos(3:4)],'Visible','on') |
---|
284 | end |
---|
285 | k = k - 1; |
---|
286 | end |
---|
287 | set(hand.hEdits(n+1:end,1:end),'Visible','off') |
---|
288 | if (~isempty(hand.checks)), set(hand.hChecks(n+1:end),'Visible','off'); end |
---|
289 | if (i == get(hand.hEdits(1,1),'UserData')) % Reached Top. Jump to there |
---|
290 | set(hand.hSlid,'Value',hand.NumRows) % This also avoids useless UIs repositioning |
---|
291 | val = hand.NumRows; |
---|
292 | end |
---|
293 | end |
---|
294 | set(hand.hSlid,'UserData',val) % Save old 'Value' |
---|
295 | |
---|
296 | % ---------------------------------------------------------------------------- |
---|
297 | function params = parse_pv_pairs(params,pv_pairs) |
---|
298 | % parse_pv_pairs: parses sets of property value pairs, allows defaults |
---|
299 | % usage: params=parse_pv_pairs(default_params,pv_pairs) |
---|
300 | % |
---|
301 | % arguments: (input) |
---|
302 | % default_params - structure, with one field for every potential |
---|
303 | % property/value pair. Each field will contain the default |
---|
304 | % value for that property. If no default is supplied for a |
---|
305 | % given property, then that field must be empty. |
---|
306 | % |
---|
307 | % pv_array - cell array of property/value pairs. |
---|
308 | % Case is ignored when comparing properties to the list |
---|
309 | % of field names. Also, any unambiguous shortening of a |
---|
310 | % field/property name is allowed. |
---|
311 | % |
---|
312 | % arguments: (output) |
---|
313 | % params - parameter struct that reflects any updated property/value |
---|
314 | % pairs in the pv_array. |
---|
315 | % |
---|
316 | % Example usage: |
---|
317 | % First, set default values for the parameters. Assume we have four |
---|
318 | % parameters that we wish to use optionally in the function examplefun. |
---|
319 | % |
---|
320 | % - 'viscosity', which will have a default value of 1 |
---|
321 | % - 'volume', which will default to 1 |
---|
322 | % - 'pie' - which will have default value 3.141592653589793 |
---|
323 | % - 'description' - a text field, left empty by default |
---|
324 | % |
---|
325 | % The first argument to examplefun is one which will always be supplied. |
---|
326 | % |
---|
327 | % function examplefun(dummyarg1,varargin) |
---|
328 | % params.Viscosity = 1; |
---|
329 | % params.Volume = 1; |
---|
330 | % params.Pie = 3.141592653589793 |
---|
331 | % |
---|
332 | % params.Description = ''; |
---|
333 | % params=parse_pv_pairs(params,varargin); |
---|
334 | % params |
---|
335 | % |
---|
336 | % Use examplefun, overriding the defaults for 'pie', 'viscosity' |
---|
337 | % and 'description'. The 'volume' parameter is left at its default. |
---|
338 | % |
---|
339 | % examplefun(rand(10),'vis',10,'pie',3,'Description','Hello world') |
---|
340 | % |
---|
341 | % params = |
---|
342 | % Viscosity: 10 |
---|
343 | % Volume: 1 |
---|
344 | % Pie: 3 |
---|
345 | % Description: 'Hello world' |
---|
346 | % |
---|
347 | % Note that capitalization was ignored, and the property 'viscosity' was truncated |
---|
348 | % as supplied. Also note that the order the pairs were supplied was arbitrary. |
---|
349 | |
---|
350 | n = length(pv_pairs) / 2; |
---|
351 | |
---|
352 | if n ~= floor(n) |
---|
353 | error 'Property/value pairs must come in PAIRS.' |
---|
354 | end |
---|
355 | if (n <= 0), return; end % just return the defaults |
---|
356 | |
---|
357 | if ~isstruct(params) |
---|
358 | error 'No structure for defaults was supplied' |
---|
359 | end |
---|
360 | |
---|
361 | % there was at least one pv pair. process any supplied |
---|
362 | propnames = fieldnames(params); |
---|
363 | lpropnames = lower(propnames); |
---|
364 | for i=1:n |
---|
365 | p_i = lower(pv_pairs{2*i-1}); |
---|
366 | v_i = pv_pairs{2*i}; |
---|
367 | |
---|
368 | ind = strmatch(p_i,lpropnames,'exact'); |
---|
369 | if isempty(ind) |
---|
370 | ind = find(strncmp(p_i,lpropnames,length(p_i))); |
---|
371 | if isempty(ind) |
---|
372 | error(['No matching property found for: ',pv_pairs{2*i-1}]) |
---|
373 | elseif (length(ind) > 1) |
---|
374 | error(['Ambiguous property name: ',pv_pairs{2*i-1}]) |
---|
375 | end |
---|
376 | end |
---|
377 | p_i = propnames{ind}; |
---|
378 | params = setfield(params,p_i,v_i); % override the corresponding default in params |
---|
379 | end |
---|