function [settings, button] = settingsdlg(varargin) % SETTINGSDLG Default dialog to produce a settings-structure % % settings = SETTINGSDLG('fieldname', default_value, ...) creates a modal % dialog box that returns a structure formed according to user input. The % input should be given in the form of 'fieldname', default_value - pairs, % where 'fieldname' is the fieldname in the structure [settings], and % default_value the initial value displayed in the dialog box. % % SETTINGSDLG uses UIWAIT to suspend execution until the user responds. % % settings = SETTINGSDLG(settings) uses the structure [settings] to form % the various input fields. This is the most basic (and limited) usage of % SETTINGSDLG. % % [settings, button] = SETTINGSDLG(settings) returns which button was % pressed, in addition to the (modified) structure [settings]. Either 'ok', % 'cancel' or [] are possible values. The empty output means that the % dialog was closed before either Cancel or OK were pressed. % % SETTINGSDLG('title', 'window_title') uses 'window_title' as the dialog's % title. The default is 'Adjust settings'. % % SETTINGSDLG('description', 'brief_description',...) starts the dialog box % with 'brief_description', followed by the input fields. % % SETTINGSDLG('windowposition', P, ...) positions the dialog box according to % the string or vector [P]; see movegui() for valid values. % % SETTINGSDLG( {'display_string', 'fieldname'}, default_value,...) uses the % 'display_string' in the dialog box, while assigning the corresponding % user-input to fieldname 'fieldname'. % % SETTINGSDLG(..., 'checkbox_string', true, ...) displays a checkbox in % stead of the default edit box, and SETTINGSDLG('fieldname', {'string1', % 'string2'},... ) displays a popup box with the strings given in % the second cell-array. % % Additionally, you can put [..., 'separator', 'seperator_string',...] % anywhere in the argument list, which will divide all the arguments into % sections, with section headings 'seperator_string'. % % You can also modify the display behavior in the case of checkboxes. When % defining checkboxes with a 2-element logical array, the second boolean % determines whether all fields below that checkbox are initially disabled % (true) or not (false). % % Example: % % [settings, button] = settingsdlg(... % 'Description', 'This dialog will set the parameters used by FMINCON()',... % 'title' , 'FMINCON() options',... % 'separator' , 'Unconstrained/General',... % {'This is a checkbox'; 'Check'}, [true true],... % {'Tolerance X';'TolX'}, 1e-6,... % {'Tolerance on Function';'TolFun'}, 1e-6,... % 'Algorithm' , {'active-set','interior-point'},... % 'separator' , 'Constrained',... % {'Tolerance on Constraints';'TolCon'}, 1e-6) % % See also inputdlg, dialog, errordlg, helpdlg, listdlg, msgbox, questdlg, textwrap, % uiwait, warndlg. % Please report bugs and inquiries to: % % Name : Rody P.S. Oldenhuis % E-mail : oldenhuis@gmail.com % Licence: 2-clause BSD (See Licence.txt) % If you find this work useful, please consider a donation: % https://www.paypal.me/RodyO/3.5 %% Initialize % errortraps narg = nargin; if verLessThan('MATLAB', '8.6') error(nargchk(1, inf, narg, 'struct')); %#ok else narginchk(1, inf); end % parse input (+errortrap) have_settings = 0; if isstruct(varargin{1}) settings = varargin{1}; have_settings = 1; end if (narg == 1) if isstruct(varargin{1}) parameters = fieldnames(settings); values = cellfun(@(x)settings.(x), parameters, 'UniformOutput', false); else error('settingsdlg:incorrect_input',... 'When pasing a single argument, that argument must be a structure.') end else parameters = varargin(1+have_settings : 2 : end); values = varargin(2+have_settings : 2 : end); end % Initialize data button = []; fields = cell(numel(parameters),1); tags = fields; % Fill [settings] with default values & collect data for ii = 1:numel(parameters) % Extract fields & tags if iscell(parameters{ii}) tags{ii} = parameters{ii}{1}; fields{ii} = parameters{ii}{2}; else % More errortraps if ~ischar(parameters{ii}) error('settingsdlg:nonstring_parameter',... 'Arguments should be given as [''parameter'', value,...] pairs.') end tags{ii} = parameters{ii}; fields{ii} = parameters{ii}; end % More errortraps if ~ischar(fields{ii}) error('settingsdlg:fieldname_not_char',... 'Fieldname should be a string.') end if ~ischar(tags{ii}) error('settingsdlg:tag_not_char',... 'Display name should be a string.') end % NOTE: 'Separator' is now in 'fields' even though % it will not be used as a fieldname % Make sure all fieldnames are properly formatted % (alternating capitals, no whitespace) if ~strcmpi(fields{ii}, {'Separator';'Title';'Description'}) whitespace = isspace(fields{ii}); capitalize = circshift(whitespace,[0,1]); fields{ii}(capitalize) = upper(fields{ii}(capitalize)); fields{ii} = fields{ii}(~whitespace); % insert associated value in output if iscell(values{ii}) settings.(fields{ii}) = values{ii}{1}; elseif (length(values{ii}) > 1) settings.(fields{ii}) = values{ii}(1); else settings.(fields{ii}) = values{ii}; end end end % Avoid (some) confusion clear parameters % Use default colorscheme from the OS bgcolor = get(0, 'defaultUicontrolBackgroundColor'); % Default fontsize fontsize = get(0, 'defaultuicontrolfontsize'); % Edit-bgcolor is platform-dependent. % MS/Windows: white. % UNIX: same as figure bgcolor % if ispc, edit_bgcolor = 'White'; % else edit_bgcolor = bgcolor; % end % TODO: not really applicable since defaultUicontrolBackgroundColor % doesn't really seem to work on Unix... edit_bgcolor = 'White'; % Get basic window properties title = getValue('Adjust settings', 'Title'); description = getValue( [], 'Description'); total_width = getValue(325, 'WindowWidth'); control_width = getValue(100, 'ControlWidth'); % Window positioning: % Put the window in the center of the screen by default. % This will usually work fine, except on some multi-monitor setups. scz = get(0, 'ScreenSize'); scxy = round(scz(3:4)/2-control_width/2); scx = min(scz(3),max(1,scxy(1))); scy = min(scz(4),max(1,scxy(2))); % String to pass on to movegui window_position = getValue('center', 'WindowPosition'); % Calculate best height for all uicontrol() control_height = max(18, (fontsize+6)); % Calculate figure height (will be adjusted later according to description) total_height = numel(fields)*1.25*control_height + ... % to fit all controls 1.5*control_height + 20; % to fit "OK" and "Cancel" buttons % Total number of separators num_separators = nnz(strcmpi(fields,'Separator')); % Draw figure in background fighandle = figure(... 'integerhandle' , 'off',... % use non-integers for the handle (prevents accidental plots from going to the dialog) 'Handlevisibility', 'off',... % only visible from within this function 'position' , [scx, scy, total_width, total_height],...% figure position 'visible' , 'off',... % hide the dialog while it is being constructed 'backingstore' , 'off',... % DON'T save a copy in the background 'resize' , 'off', ... % but just keep it resizable 'renderer' , 'zbuffer', ... % best choice for speed vs. compatibility 'WindowStyle' ,'modal',... % window is modal 'units' , 'pixels',... % better for drawing 'DockControls' , 'off',... % force it to be non-dockable 'name' , title,... % dialog title 'menubar' ,'none', ... % no menubar of course 'toolbar' ,'none', ... % no toolbar 'NumberTitle' , 'off',... % "Figure 1.4728...:" just looks corny 'color' , bgcolor); % use default colorscheme %% Draw all required uicontrols(), and unhide window % Define X-offsets (different when separators are used) separator_offset_X = 2; if num_separators > 0 text_offset_X = 20; text_width = (total_width-control_width-text_offset_X); else text_offset_X = separator_offset_X; text_width = (total_width-control_width); end % Handle description description_offset = 0; if ~isempty(description) % create textfield (negligible height initially) description_panel = uicontrol(... 'parent' , fighandle,... 'style' , 'text',... 'Horizontalalignment', 'left',... 'position', [separator_offset_X,... total_height,total_width,1]); % wrap the description description = textwrap(description_panel, {description}); % adjust the height of the figure textheight = size(description,1)*(fontsize+6); description_offset = textheight + 20; total_height = total_height + description_offset; set(fighandle,... 'position', [scx, scy, total_width, total_height]) % adjust the position of the textfield and insert the description set(description_panel, ... 'string' , description,... 'position', [separator_offset_X, total_height-textheight, ... total_width, textheight]); end % Define Y-offsets (different when descriptions are used) control_offset_Y = total_height-control_height-description_offset; % initialize loop controls = zeros(numel(tags)-num_separators,1); ii = 1; sep_ind = 1; enable = 'on'; separators = zeros(num_separators,1); % loop through the controls if numel(tags) > 0 while true % Should we draw a separator? if strcmpi(tags{ii}, 'Separator') % Print separator uicontrol(... 'style' , 'text',... 'parent' , fighandle,... 'string' , values{ii},... 'horizontalalignment', 'left',... 'fontweight', 'bold',... 'position', [separator_offset_X,control_offset_Y-4, ... total_width, control_height]); % remove separator, but save its position fields(ii) = []; tags(ii) = []; separators(sep_ind) = ii; values(ii) = []; sep_ind = sep_ind + 1; % reset enable (when neccessary) if strcmpi(enable, 'off') enable = 'on'; end % NOTE: DON'T increase loop index % ... or a setting? else % logicals: use checkbox if islogical(values{ii}) % First draw control controls(ii) = uicontrol(... 'style' , 'checkbox',... 'parent' , fighandle,... 'enable' , enable,... 'string' , tags{ii},... 'value' , values{ii}(1),... 'position', [text_offset_X,control_offset_Y-4, ... total_width, control_height]); % Should everything below here be OFF? if (length(values{ii})>1) % turn next controls off when asked for if values{ii}(2) enable = 'off'; end % Turn on callback function set(controls(ii),... 'Callback', @(varargin) EnableDisable(ii,varargin{:})); end % doubles : use edit box % cells : use popup % cell-of-cells: use table else % First print parameter uicontrol(... 'style' , 'text',... 'parent' , fighandle,... 'string' , [tags{ii}, ':'],... 'horizontalalignment', 'left',... 'position', [text_offset_X,control_offset_Y-4, ... text_width, control_height]); % Popup, edit box or table? style = 'edit'; draw_table = false; if iscell(values{ii}) style = 'popup'; if all(cellfun('isclass', values{ii}, 'cell')) draw_table = true; end end % Draw appropriate control if ~draw_table controls(ii) = uicontrol(... 'enable' , enable,... 'style' , style,... 'Background', edit_bgcolor,... 'parent' , fighandle,... 'string' , values{ii},... 'position', [text_width,control_offset_Y,... control_width, control_height]); else % TODO % ...table? ...radio buttons? How to do this? warning(... 'settingsdlg:not_yet_implemented',... 'Treatment of cells is not yet implemented.'); end end % increase loop index ii = ii + 1; end % end loop? if ii > numel(tags) break, end % Decrease offset control_offset_Y = control_offset_Y - 1.25*control_height; end end % Draw cancel button uicontrol(... 'style' , 'pushbutton',... 'parent' , fighandle,... 'string' , 'Cancel',... 'position', [separator_offset_X,2, total_width/2.5,control_height*1.5],... 'Callback', @Cancel) % Draw OK button uicontrol(... 'style' , 'pushbutton',... 'parent' , fighandle,... 'string' , 'OK',... 'position', [total_width*(1-1/2.5)-separator_offset_X,2, ... total_width/2.5,control_height*1.5],... 'Callback', @OK) % move to center of screen and make visible movegui(fighandle, window_position); set(fighandle, 'Visible', 'on'); % WAIT until OK/Cancel is pressed uiwait(fighandle); %% Helper funcitons % Get a value from the values array: % - if it does not exist, return the default value % - if it exists, assign it and delete the appropriate entries from the % data arrays function val = getValue(default, tag) index = strcmpi(fields, tag); if any(index) val = values{index}; values(index) = []; fields(index) = []; tags(index) = []; else val = default; end end %% callback functions % Enable/disable controls associated with (some) checkboxes function EnableDisable(which, varargin) %#ok % find proper range of controls to switch if (num_separators > 1) range = (which+1):(separators(separators > which)-1); else range = (which+1):numel(controls); end % enable/disable these controls if strcmpi(get(controls(range(1)), 'enable'), 'off') set(controls(range), 'enable', 'on') else set(controls(range), 'enable', 'off') end end % OK button: % - update fields in [settings] % - assign [button] output argument ('ok') % - kill window function OK(varargin) %#ok % button pressed button = 'OK'; % fill settings for i = 1:numel(controls) % extract current control's string, value & type str = get(controls(i), 'string'); val = get(controls(i), 'value'); style = get(controls(i), 'style'); % popups/edits if ~strcmpi(style, 'checkbox') % extract correct string (popups only) if strcmpi(style, 'popupmenu'), str = str{val}; end % try to convert string to double val = str2double(str); % insert this double in [settings]. If it was not a % double, insert string instead if ~isnan(val), settings.(fields{i}) = val; else settings.(fields{i}) = str; end % checkboxes else % we can insert value immediately settings.(fields{i}) = val; end end % kill window delete(fighandle); end % Cancel button: % - assign [button] output argument ('cancel') % - delete figure (so: return default settings) function Cancel(varargin) %#ok button = 'cancel'; delete(fighandle); end end