source: MML/trunk/applications/common/tablegui.m @ 4

Last change on this file since 4 was 4, checked in by zhangj, 10 years ago

Initial import--MML version from SOLEIL@2013

File size: 17.9 KB
Line 
1function 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
91hand.NumRows = 12;          hand.NumCol = 6;
92hand.MAX_ROWS = 10;         hand.left_marg = 10;
93hand.RowHeight = 20;        hand.bd_size = 0;
94hand.checks = '';           hand.HdrButtons = 'Y';
95hand.HorAlin = 'center';    hand.FigName = 'Table';
96hand.array = cell(hand.NumRows,hand.NumCol);
97hand.modal = 'y';           hand.position = 'east';
98hand.RowNumbers = '';       d_col = 0;
99
100if (nargin == 0)        % Demo
101    hand.ColNames = {'A' 'B' 'C' 'D' 'E' 'F'};
102    hand.ColWidth = [50 80 80 80 80 50];
103else
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
145end
146
147arr_pos_xi = [hand.left_marg + [0 (cumsum(hand.ColWidth+hand.bd_size))]];
148arr_pos_xi(end) = [];      % We don't want the last element
149arr_pos_xw = hand.ColWidth;
150
151% ---------------- Create the figure ----------------------------------
152fig_height = min(hand.NumRows,hand.MAX_ROWS) * (hand.RowHeight+hand.bd_size);
153if (~isempty(hand.HdrButtons)),    fig_height = fig_height + 22;   end     % Make room for header buttons
154if (~isempty(hand.modal)),          fig_height = fig_height + 30;   end     % Make room for OK,Cancel buttons
155pos = [5 75 sum(arr_pos_xw)+hand.left_marg+(hand.NumCol-1)*hand.bd_size+15 fig_height];  % The 15 is for the slider
156if (~isempty(hand.checks)),     pos(3) = pos(3) + 15;   end         % Account for checkboxes size
157hand.hFig = figure('unit','pixels','NumberTitle','off','Menubar','none','resize','on','position', ...
158    pos,'Name',hand.FigName,'Resize','off','Visible','off');
159movegui(hand.hFig,hand.position)
160
161hand.arr_pos_y = (fig_height-hand.RowHeight-hand.bd_size - (0:hand.NumRows-1)*(hand.RowHeight+hand.bd_size))';
162if (~isempty(hand.HdrButtons)),     hand.arr_pos_y = hand.arr_pos_y - 22;   end
163
164if (~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];
168end
169hand.hEdits = zeros(hand.NumRows,hand.NumCol);
170hand.Edits_pos_orig = cell(hand.NumRows,hand.NumCol);
171
172% ---------------- Create the edit uicontrols ---------------------------
173for (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
189end
190if (~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
195end
196
197% ---------------- See if we need a slider ---------------------------
198pos_t = get(hand.hEdits(1,hand.NumCol),'pos');       % Get top right edit position
199pos_b = get(hand.hEdits(hand.MAX_ROWS,1),'pos');    % Get last visible edit position
200if (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
212end
213
214% ---------------- See if the window is MODAL ---------------------------
215if (~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
239else
240    set(hand.hFig,'Visible','on','UserData',hand)
241    if (nargout),   out = hand.hFig;    end
242end
243
244% ---------------------------------------------------------------------------
245function slider_Callback(obj,event,hand)
246
247val = round(get(hand.hSlid,'Value'));
248old_val = get(hand.hSlid,'UserData');
249ds = val - old_val;
250
251if (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
270elseif (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
293end
294set(hand.hSlid,'UserData',val)                      % Save old 'Value'
295
296% ----------------------------------------------------------------------------
297function 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
350n = length(pv_pairs) / 2;
351
352if n ~= floor(n)
353    error 'Property/value pairs must come in PAIRS.'
354end
355if (n <= 0),    return;     end     % just return the defaults
356
357if ~isstruct(params)
358    error 'No structure for defaults was supplied'
359end
360
361% there was at least one pv pair. process any supplied
362propnames = fieldnames(params);
363lpropnames = lower(propnames);
364for 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
379end
Note: See TracBrowser for help on using the repository browser.