classdef (Sealed) VisualUpdater < handle
    %VisualUpdater   Define the VisualUpdater class.
    %
    %    VisualUpdater methods:
    %        attach - Attach a source to the updater.
    %        detach - Detach a source from the updater.
    %
    %    VisualUpdater properties:
    %        Instance - The Singleton instance of the updater.
    
    %   Copyright 2009-2011 The MathWorks, Inc.
    
    % Create a Constant, Hidden property to store the Instance.  This means
    % it is set once at class read-time enabling this class to be Singleton
    % (along with the private constructor).  It is Hidden so that it does
    % not show up in the default display.
    properties (Hidden, Constant)
        
        % Instance is a Constant property which stores the singleton
        % instance to the uiscopes.VisualUpdater object.
        Instance = uiscopes.VisualUpdater;
    end
    
    properties (SetAccess = private)
        
        % Sources hold all of the source objects that have been attached.
        Sources = [];
        Count = 0;
    end
    
    properties
        Period = .05;
    end
    
    properties (Access = private)
        
        % Timer holds the handle to a timer object that is used to
        % periodically update all simulink source Visual fields.
        Timer = [];
    end
    
    methods (Access = private)
        
        function this = VisualUpdater
            %VisualUpdater   Construct the VisualUpdater class.
            
            % Call MLOCK to avoid clear classes warnings.
            mlock;
        end
    end
    
    methods
        
        function attach(this, hSource)
            
            % Add the new source to the list of sources.
            this.Sources = union(this.Sources, hSource);
            
            % Get the stored timer and make sure that it is running.
            modelTimer = this.Timer;
            
            if isempty(modelTimer)
                modelTimer = timer( ...
                    'ExecutionMode', 'fixedRate', ...
                    'Period', this.Period, ...
                    'TimerFcn', makeTimerCallback(this));
                this.Timer = modelTimer;
                
                start(modelTimer);
            end
        end
        
        function detach(this, hSource)
            
            if nargin > 1
                
                oldSources = this.Sources;
                newSources = setdiff(oldSources, hSource);
                
                % Remove the passed source from the stored sources vector.
                this.Sources = newSources;
            else
                
                % If no source is passed, we detach them all.
                this.Sources = [];
            end
            
            % If we have no more sources, or no source was passed, stop the
            % timer and reset the count back to 0.
            if isempty(this.Sources)
                
                this.Count = 0;
                
                % If the timer is running, stop it.
                hTimer = this.Timer;
                if ~isempty(hTimer)
                    this.Timer = [];
                    try
                        if strcmp(hTimer.Running, 'on')
                            stop(hTimer);
                        end
                        delete(hTimer);
                    catch ME
                        
                        % If the error thrown is that the timer is invalid,
                        % just ignore it, but rethrow any other errors.
                        if ~strcmp(ME.identifier, 'MATLAB:timer:invalid')
                            rethrow(ME);
                        end
                    end
                end
            end
        end
    end
end

% -------------------------------------------------------------------------
function cb = makeTimerCallback(this)

% Create the callback.  This local function insures that there are no extra
% variables passed in the anonymous workspace.
cb = @(varargin) lclUpdate(this);

end

% -------------------------------------------------------------------------
function lclUpdate(this)

hSources = this.Sources;

% If we have no attached sources, stop the timer via the detach method.
if isempty(hSources)
    detach(this);
    return;
end

this.Count = this.Count+1;

% We only update the time status no faster than 2 Hz regardless of how fast
% we update the visual.
shouldUpdateTimeStatus = this.Count*this.Period > .5;

for indx = 1:numel(hSources)
    try
        forceUpdate(hSources(indx).Updater);
        if shouldUpdateTimeStatus
            
            % Reset the count so that we can keep track of how long it has
            % been since we updated the time status.
            this.Count = 0;
            
            % Update the time status of the source and any text on the
            % visual.
            updateText(hSources(indx));
        end
    catch ME %#ok
        % If an error occurs while updating the time status, remove the
        % source which caused the error.
        detach(this, hSources(indx));
    end
end
end

% [EOF]
