Code covered by the BSD License  

Highlights from
SPINNER

SPINNER

by

 

15 Sep 2004 (Updated )

Create a simple spinner control using 3 uicontrols.

spinner(varargin)
function varargout = spinner(varargin)
%SPINNER create a 'spinner' from an edit box and two pushbuttons
% SPINNER(PROP1,VAL1,PROP2,VAL2,...) creates the spinner with the specified
%  property values.
%  Properties:
%    Position : pixel position rectangle, default [20 20 60 20]
%    Min : minimum value, default 0
%    Max : maximum value, default 1
%    StartValue : initial value, default 1
%    Step : increment value, default 0.1
%    Callback : callback (can be a string, function handle, 
%               or cell array with a function handle), default ''
%    Tag : name for spinner object.
% H = SPINNER returns the handle for the edit box that contains the value.
%  Access it's String or Value property to get the string or value from the
%  'spinner'.
% [H,H2] = SPINNER returns the handles for the pushbuttons in H2.
% S = SPINNER(OBJ) returns the spinner data for the figure.  If there are
%  multiple spinners in the figure, then passing in the figure handle will
%  return an array of structures.  Passing in the handle of one of the
%  uicontrols that comprise a particular spinner will return the structure
%  for that spinner only.
%
% Example:
% 
%    fig = figure;
%    h = spinner('Parent',fig,...
%                'Position',[100 100 60 40],...
%                'Min',0,...
%                'Max',1000000,...
%                'StartValue',1,...
%                'Step',100,...
%                'Callback',@(h,e));



% quick return, if they want the spinner info
if nargin == 1 && nargout == 1
    varargout{1} = spinnerdata(varargin{1});
    return
end
    



% list of valid properties
validprops = {'position';...
              'min';...
              'max';...
              'startvalue';...
              'step';...
              'parent';...
              'callback';...
              'tag'};
          
% default values              
pos = [20 20 60 20];
minimum = 0;
maximum = 1;
step = 0.1;
start_value = 1;
parent = [];
callback = '';
tag = 'spinner';

if mod(nargin,2) ~= 0
    error('Incorrect number of inputs.  Must be an even number of inputs.');
end

% parse inputs
for n = 1:2:nargin
    prop = lower(varargin{n});
    val = varargin{n+1};
    
    ind = strmatch(prop,validprops);
    if isempty(ind)
        ind = 0;
    end
    
    switch ind
        case 1 %Position
            if ~isnumeric(val) || any(size(val) ~= [1,4])
                error('Position must be numeric, 1 by 4 vector.')
            end
            pos = val;
        case 2 %Min
            if ~isnumeric(val) || ~isfinite(val) || numel(val) ~= 1
                error('Min must be a finite numeric scalar.')
            end
            minimum = val;
        case 3 %Max       
            if ~isnumeric(val) || ~isfinite(val) || numel(val) ~= 1
                error('Max must be a finite numeric scalar.')
            end
            maximum = val;
        case 4 %StartValue
            if ~isnumeric(val) || ~isfinite(val) || numel(val) ~= 1
                error('StartValue must be a finite numeric scalar.')
            end
            start_value = val;
        case 5 %Step
            if ~isnumeric(val) || ~isfinite(val) || numel(val) ~= 1
                error('Step must be a finite numeric scalar.')
            end
            step = val;
        case 6 %Parent
            if isempty(val) || ~ishandle(val) || ~any(strcmp(get(val,'Type'),{'figure';'uipanel'}))
                parent = val;
            end
        case 7 %Callback
            if ~ischar(val) && ~isa(val,'function_handle') && ...
               (~iscell(val) || ...
                  (~ischar(val{1})  && ~isa(val{1},'function_handle')))
                  error(['Callback must be a string, function handle, ' ...
                          'or a cell array with a first element that is a ' ...
                          'string or function handle.']);
            end
            callback = val;
        case 8 %Tag
            if ~ischar(val)
                error('Tag must be a string.')
            end
            tag = val;
        otherwise
            error('Unrecognized property "%s".',varargin{n});
    end
end

% more error checking
if minimum >= maximum
    error('Min must be less than Max.');
end

if start_value <minimum || start_value > maximum
    error('StartValue must be between Min and Max.')
end

% Parent not specified, use current figure
if isempty(parent)
    parent = gcf;
end

% size for pushbuttons
push_width = 20;
push_height = round(pos(4)/2);
button_width = push_width;
if mod(button_width,2) ~= 0;
    button_width = push_width - 1;    
end

% put background color in proper orientation 1 by 1 by3
uicontrolcolor = reshape(get(0,'defaultuicontrolbackgroundcolor'),[1,1,3]);

% array for pushbutton's CData
button_size = 16;
mid = button_size/2;
push_cdata = repmat(uicontrolcolor,button_size,button_size);

% create arrow shape
for r = 4:11
    start = mid - r + 8 ;
    last = mid + r - 8;
    push_cdata(r,start:last,:) = 0;
end

% create uicontrols
h_edit = uicontrol('Units','pixels',...
                 'Position',pos,...
                 'Style','edit',...
                 'Tag',[tag '_edit'],...
                 'String',num2str(start_value),...
                 'Value',start_value,...
                 'Min',minimum,...
                 'Max',maximum,...
                 'Parent',parent,...
                 'HorizontalAlignment','left');
if ispc
    set(h_edit,'BackGroundColor',[1 1 1])
end
             
h_down = uicontrol('Units','pixels',...
                 'Position',[pos(1) + (pos(3) - push_width) - 2, pos(2) + 2, push_width, push_height - 2],...
                 'CData',flipdim(push_cdata,1),...
                 'Tag',[tag '_down'],...
                 'Parent',parent,...
                 'SelectionHighlight','off');
h_up = uicontrol('Units','pixels',...
                 'Position',[pos(1) + (pos(3) - push_width) - 2, pos(2) + push_height, push_width, pos(4) - push_height - 2],...
                 'CData',push_cdata,...
                 'Tag',[tag '_up'],...
                 'Parent',parent,...
                 'SelectionHighlight','off');
             
% structure with useful info                 
spinner_struct.edit = h_edit;
spinner_struct.down = h_down;
spinner_struct.up = h_up;
spinner_struct.step = step;
spinner_struct.start_value = start_value;
spinner_struct.last_valid_value = start_value;
spinner_struct.callback = callback;
spinner_struct.tag = tag;

% store useful info, SPINNERDATA is like a simple GUIDATA
spinnerdata(h_edit,spinner_struct);

% callbacks
set(h_down,'Callback',@increment_down);
set(h_up,'Callback',@increment_up);
set(h_edit,'KeyPressFcn',@edit_keypress);

%outputs
if(nargout)
    varargout{1} = h_edit;
    varargout{2} = [h_down; h_up];
end

% ---------------------------------------------------------
function edit_keypress(h,e)
%EDIT_KEYPRESS KeyPressFcn for the edit window

% get useful info
s = spinnerdata(h);

% get information about KeyPress event
c = e.Character;
k = e.Key;
str = get(h,'String');

% valid number characters
numbers = {'0';'1';'2';'3';'4';'5';'6';'7';'8';'9'};

if strcmp(k,'backspace')
    % if it's a backspace, remove the last character
    if numel(str) > 0
        str = str(1:end-1);
    end
elseif any(strcmp(c,numbers)) || strcmp(c,'.')
    % aonly allow number or '.'
    str = [str c];
end

% check the values
[v,str] = check_value(str,s);
set(h,'Value',v);

% switch the focus, then back, so setting the String has an effect.
uicontrol(s.up);
uicontrol(h);
set(h,'String',str);

% execute the callback
execute_callback(s);

% ---------------------------------------------------------
function execute_callback(s)
%EXECUTE_CALLBACK execute the callback

% only execute if there's something in the edit window
if ~isempty(get(s.edit,'String'))
    if ischar(s.callback)
        evalin('base',s.callback);
    elseif isa(s.callback,'function_handle')
        feval(s.callback,gcbo,[])
    elseif iscell(s.callback)
        feval(s.callback{:})
    end
end

% ---------------------------------------------------------
function [v,str] = check_value(str,s)
%CHECK_VALUE make sure the entry is a valid number

if ~isnumeric(str)
    % if it's a string, convert to get numeric value
    v = str2num(str);
else
    % otherwise, reassign the value, convert number to string
    v = str;
    str = num2str(v);
end

% return early if the string wasn't a valid number
if isempty(v) || isempty(str)
    return
end

minimum = get(s.edit,'Min');
maximum = get(s.edit,'Max');

% make sure value is in range
if v < minimum
    v = minimum;
    str = num2str(v);
elseif v > maximum;
    v = maximum;
    str = num2str(v);
end

% store the last valid value
s.last_valid_value = v;
spinnerdata(s.edit,s);


% ---------------------------------------------------------
function increment_down(h,e)
%INCREMENT_DOWN Callback for down pushbutton

%get useful info
s = spinnerdata(h);

% get string, convert to number, reduce by step
str = get(s.edit,'String');
v = str2num(str);
v = v-s.step;

% check the values
[v,str] = check_value(v,s);

% set the String and Value
set(s.edit,'String',num2str(v),'Value',v);

% execute the callbacks
execute_callback(s);

% ---------------------------------------------------------
function increment_up(h,e)
%INCREMENT_DOWN Callback for up pushbutton

%get useful info
s = spinnerdata(h);

% get string, convert to number, reduce by step
str = get(s.edit,'String');
v = str2num(str);
v = v+s.step;

% check the values
[v,str] = check_value(v,s);

% set the String and Value
set(s.edit,'String',num2str(v),'Value',v);

% execute the callbacks
execute_callback(s);

% ---------------------------------------------------------
function s = spinnerdata(h,val)
%SPINNERDATA store/return stored value

if ~strcmp(get(h,'Type'),'figure')
    fig = ancestor(h,'figure');
else 
    fig = h;
end

if nargin == 1 && nargout == 1
    s = getappdata(fig,get_spinnerdata_name);
    if numel(s) > 1
        for n = 1:length(s)
            hndls = [s(n).edit;s(n).down;s(n).up];
            if any(h == hndls)
                s = s(n);
                return
            end
        end
    end    
elseif nargin == 2
    s = getappdata(fig,get_spinnerdata_name);
    if isempty(s)
        setappdata(fig,get_spinnerdata_name,val);
    else
        edit_windows = [s.edit];
        ind = find(val.edit == edit_windows);
        if isempty(ind)
            s(end+1) = val;
        else
            s(ind) = val;
        end
        setappdata(fig,get_spinnerdata_name,s);
    end
end


% ---------------------------------------------------------
function str = get_spinnerdata_name
%GET_SPINNERDATA_NAME return name used for appdata field

str = 'SpinnerAppData';

Contact us