classdef (Sealed) ToggleMenu < spcwidgets.AbstractWidget
    %ToggleMenu   Define the UIMenuToggle class.
   
    %   Copyright 2009-2011 The MathWorks, Inc.

    properties (SetAccess = private)     
        Type = 'spcwidgets.ToggleMenu';
        hmenu;        
    end
    
    properties        
        Parent;        
        Callback;        
    end
    
    properties (SetObservable, AbortSet)
        Accelerators;
        Labels;
        Test = 1;
    end
    
    properties(Dependent, SetObservable, AbortSet)
        Interruptible;
        Checked;
        Enable; 
        Separator;
        Visible;
        Tag;       
    end
    
    methods        
        function this = ToggleMenu(hParent, varargin)
            %ToggleMenu   Construct the ToggleMenu class.
                        
            mlock;
            
            % Instantiate menu widget
            this.Parent = hParent;
            this.hmenu = uimenu(this.Parent, ...
                'Callback',@(p,e) execCallback(this));
                        
            % Set optional property values for this object
            if ~isempty(varargin)
                set(this,varargin{:});
            end
            
            localAutoUpdate(this);
        end        
        
        function set.Labels(this,val)
            % Set-function for Labels property
            %  - must be a string or cell-array of strings
            %  - an empty string is allowed
            
            % if a single string is passed, wrap in a cell
            if ischar(val) || (isstring(val) && isscalar(val))
                val = {val};            
            end
            % Validate setting
            if ~iscell(val) && ~(isstring(val) && isscalar(val))
                error(message('Spcuilib:scopes:LabelsMustBeString'));
            end
            numLabels = numel(val);
            for i=1:numLabels
                if ~ischar(val{i}) && ~(isstring(val{i}) && isscalar(val{i}))
                    error(message('Spcuilib:scopes:LabelsMustBeString'));
                end
            end
            
            % Cross-check with # of Accelerators
            %  - scalar expansion demands 1 or N labels, where N=# accel
            %  - empty/zero entries is treated like 1 entry
            numAccel = numel(this.Accelerators);  %#ok<*MCSUP>
            if (numLabels~=numAccel) && (numLabels>1) && (numAccel>1)
                error(message('Spcuilib:scopes:NumLabelsAndAccel',numLabels,numAccel));
            end
            this.Labels = val;
            
            localAutoUpdate(this)
        end

        function set.Accelerators(this,val)
            % Set-function for Labels property
            %  - must be a string or cell-array of strings
            %  - an empty string is allowed
            
            % if a single string is passed, wrap in a cell
            if ischar(val) || (isstring(val) && isscalar(val))
                val ={val};
            end
            % Validate setting
            if ~iscell(val)
                error(message('Spcuilib:scopes:AcceleratorsMustBeString'));
            end
            numAccel = numel(val);
            for i=1:numAccel
                if ~ischar(val{i}) && ~(isstring(val) && isscalar(val))
                    error(message('Spcuilib:scopes:AcceleratorsMustBeString'));
                end
            end
            
            % Cross-check with # of Labels
            %  - scalar expansion demands 1 or N accels, where N=# accel
            %  - empty/zero entries is treated like 1 entry
            numLabels = numel(this.Labels); 
            if (numLabels~=numAccel) && (numLabels>1) && (numAccel>1)
                error(message('Spcuilib:scopes:NumLabelsAndAccel',numLabels,numAccel));
            end
            this.Accelerators = val;
            
            localAutoUpdate(this);
        end
               
        function set.Enable(this, v)
            set(this.hmenu, 'Enable', v);
        end
        
        function v = get.Enable(this)
            v = get(this.hmenu, 'Enable');
        end
        
        function set.Separator(this, v)
            set(this.hmenu, 'Separator', v);
        end
        
        function v = get.Separator(this)
            v = get(this.hmenu, 'Separator');
        end
        
        function set.Visible(this, v)
            set(this.hmenu, 'Visible', v);
        end
        
        function v = get.Visible(this)
            v = get(this.hmenu, 'Visible');
        end
        
        function set.Tag(this, v)
            set(this.hmenu, 'Tag', v);
        end
        
        function v = get.Tag(this)
            v = get(this.hmenu, 'Tag');
        end
  
        function set.Checked(this, v)
            set(this.hmenu, 'Checked', v);
            localAutoUpdate(this);
        end
        
        function v = get.Checked(this)
            v = get(this.hmenu, 'Checked');
        end
        
        function v = get.Interruptible(this)
          v = get(this.hmenu, 'Interruptible');
        end
        
        function set.Interruptible(this, v)
          set(this.hmenu, 'Interruptible', v);
        end
        
        function delete(h)            
            % Handle could have been "pulled out" from under us            
            if uiscopes.isHandle(h.hmenu)
                delete(h.hmenu);
            end
            h.hmenu=[];
        end
    end
    
    methods (Access = 'protected')
        function execCallback(this)
            % Execute callback and side-effect (local update)
            %  as dispatched from underlying uimenu
            
            % Toggle checked state
            if strcmpi(this.Checked,'on')
                s='off';
            else
                s='on';
            end
            this.Checked = s;
                        
            % Note:
            % A listener of "Checked" state may have changed
            % the state on us, say, to implement a constraint
            % on whether the item can be turned off.
            %
            % So we must re-confirm that Checked has changed
            % If the current Checked state matches "s",
            % it has changed.  If is has not, skip:
            if strcmpi(this.Checked,s)
                % Change in state occurred
                                
                % Execute callback
                if ~isempty(this.Callback)
                    if ischar(this.Callback) || (isstring(this.Callback) && isscalar(this.Callback))
                        eval(this.Callback);
                    else
                        % Function handle:
                        %
                        % Pass two standard args (just like HG):
                        %  handle to Parent object, and
                        %  event structure (which is empty here)
                        feval(this.Callback,this,[]);
                    end
                end
            end
        end
    end
end

function localAutoUpdate(this)
% Change selection based on Checked

v = this.Checked;
switch v
    case 'on'
        select = 1;
    otherwise
        % May only be one selection
        select = max([1 numel(this.Labels) numel(this.Accelerators)]);
end

% Change menu accelerator, implement scalar-expansion
i=min(select, numel(this.Accelerators));
if i>0 % if i==0, leave HG default
    set(this.hmenu,'Accelerator',this.Accelerators{i});
end

% Change menu label, implement scalar-expansion
% Could be zero as well
i=min(select, numel(this.Labels));
if i>0 % if i==0, leave HG default
    set(this.hmenu,'Label',this.Labels{i});
end
end

% [EOF]
