classdef (Sealed) Status < spcwidgets.AbstractWidget
    %Status   Status class
    %
    %   Status methods:
    %      Status - Construct Status
    %      truncate - Truncate text if it overruns control
    %
    %   Status properties:
    %      Text - String to display in status text area
    %      Width - Width of status text area in pixels (defaults to 40)
    %      AutoGrow - Whether to adjust Width according to text extent (defaults to false)
    %      AutoGrowMin - Minimum Width to allow when AutoGrow is true (defaults to 40)
    %      AutoGrowMax - Maximum Width to allow when AutoGrow is true (defaults to 200)
    %      Enable - Whether status text area is enabled
    %      Visible - Whether status text area is visible
    %      Tag - Status text tag
    %      Tooltip - Tooltip to display on status text mouse hover
    %      Callback - Function to execute on status text button down
    %
    %   See also UIMGR.UISTATUS
    
    %   Copyright 2009-2019 The MathWorks, Inc.
    
    properties(SetObservable)
        Text = ''; % String to display in status text area
        Width = 40; % Width of status text area
        AutoGrow = false; % Whether to adjust Width according to text extent
        AutoGrowMin = 40; % Minimum Width to allow when AutoGrow is true
        AutoGrowMax = 200;  % Maximum Width to allow when AutoGrow is true
        AutoGrowAllowSkips = true; % Whether to allow AutoGrow skips for performance
        Tag = 'Status'; % Status text tag
        UIContextMenu;
        Placement = 0;
    end
    
    properties(SetObservable)
        Enable   = 'on'; % Whether status text area is enabled
        Visible  = 'on'; % Whether status text area is visible
        Tooltip  = ''; % Tooltip to display on status text mouse hover
        Callback = ''; % Function to execute on status text button down
    end
    
    properties(Access = private)
        ParentHandle;
        ContainerHandle;
        TextHandle;
        PixelFactor;
        Height;
        AutoGrowCounter = 1;
    end
        
    methods
        function obj = Status(parent, varargin)
            %Status   Construct Status
            %   Status(PARENT, P1, V1, ...) constructs a Status object and
            %   creates a status text in PARENT, where PARENT must be a
            %   spcwidgets.StatusBar object.  The Status property value
            %   pairs P1, V1, etc are applied to the object.
            %
            %   This class should not typically be used directly but is
            %   created on render by uimgr.uistatus.
            %
            %   See also UIMGR.UISTATUS
            
            mlock;
            
            % Check parent
            if ~isa(parent,'spcwidgets.StatusBar')
                error(message('Spcuilib:uimgr:ErrorInvalidParentStatusBar'));
            end
            
            % Get constants from parent
            obj.ParentHandle = parent;
            obj.PixelFactor  = parent.PixelFactor;
            obj.Height       = parent.Height;
                        
            % Set optional property values for this object
            if ~isempty(varargin)
                set(obj, varargin{:});
            end
        end
        
        function delete(obj)
            % Status destructor
            
            % Delete container, which also deletes contained objects
            if uiscopes.isHandle(obj.ContainerHandle)
                delete(obj.ContainerHandle);
            end
            obj.ContainerHandle = [];
            obj.TextHandle = [];
        end
        
        function truncate(obj)
            % truncate   Truncate text if it overruns control
            
            hStatusText = obj.TextHandle;
            
            % Get width and extent of text
            pos = get(obj.ContainerHandle, 'Position');
            ext = get(hStatusText, 'Extent');
            pos(3) = pos(3)-4;
            if ext(3) > pos(3)
                
                textStr = obj.Text;
                
                % Replace last 3 chars with ellipsis
                textStr(end-2:end) = '...';
                
                % Shrink text until we can fit it
                while numel(textStr) > 3
                    set(hStatusText, 'String', textStr);
                    ext = get(hStatusText, 'Extent');
                    if ext(3) <= pos(3)
                        break;
                    end
                    textStr(end-3) = ''; % Remove one char before ellipsis
                end
                
                % If just the ellipsis fits, make string empty
                if numel(textStr) <= 3
                    textStr = '';
                end
                
                % Apply truncated string
                obj.Text = textStr;
            end
        end
        
        function set.UIContextMenu(obj, h)
            obj.UIContextMenu = h;
            set(obj.TextHandle, 'UIContextMenu', h);
        end
                
        function set.Text(obj, v)
            obj.Text = v;
            obj.applyText;
        end
        
        function set.Width(obj, v)
            obj.Width = v;
            obj.applyWidth;
            obj.applyText;
        end
        
        function set.Enable(obj, v)
            obj.Enable = v;
            set(obj.TextHandle, 'Enable', v);
        end
        
        function set.Visible(obj, v)
            prevValue = obj.Visible;
            obj.Visible = v;
            set(obj.ContainerHandle,'Visible',v);
            if ~strcmp(prevValue,v)
                % Align the widgets next to each other whenver we change
                % the visibility to avoid having empty spaces between them
                statusFlowHandle = get(obj.ContainerHandle, 'Parent');
                if ~isempty(statusFlowHandle)
                    statusFlowHandle.ResizeFcn(statusFlowHandle);
                end
            end
        end
        
        function set.Tag(obj, v)
            obj.Tag = v;
            set(obj.ContainerHandle, 'Tag', [v 'Container']); %#ok<*MCSUP>
            set(obj.TextHandle, 'Tag', v);
        end
        
        function set.Tooltip(obj, v)
            obj.Tooltip = v;
            set(obj.TextHandle, 'TooltipString',v);
        end
        
        function set.Callback(obj, v)
            obj.Callback = v;
            set(obj.TextHandle, 'ButtonDownFcn',v);
        end
        
        function hText = copy(obj, newParent)
            
            % The TextHandle is a uicontrol, which would not show when
            % printed from an invisible figure (g772412).  As a result,
            % create an axes and add a text object to it.
            hTextAxes = axes(...
                'Parent', newParent, ...
                'Visible', 'off', ...
                'Units', 'pixels', ...
                'Tag', 'TimeOffsetAxes', ...
                'HandleVisibility', 'off', ...
                'Position', [4 2 1 1]);
            hText = text(0, 0, obj.Text, ...
                'FontSize', get(0,'FactoryUIControlFontSize'), ...
                'Units', 'pixels', ...
                'VerticalAlignment', 'Bottom', ...
                'Tag', 'TimeOffsetStatus', ...
                'Parent', hTextAxes);
        end
    end
    
    methods (Hidden)
        function varargout = forceRender(obj)
            % Get constants from parent
            statusHeight = obj.Height;
            
            hContainer = obj.ContainerHandle;
            if isempty(hContainer)
                defaultWidth = obj.Width * obj.PixelFactor-1;
                
                % existingChildren = obj.ParentHandle.ContainerHandle.Children;
                
                statusBar = obj.ParentHandle.ContainerHandle;
                
                hContainer = uipanel('Parent', statusBar, ...
                    'Tag', [obj.Tag 'Container'], ...
                    'BorderType', 'beveledin', ...
                    'Visible', obj.Visible, ...
                    'Units', 'Pixels', ...
                    'Position', [1 3 defaultWidth statusHeight-2], ...
                    'UserData', obj.Placement, ...
                    'HitTest', 'off');
                 
                obj.ContainerHandle = hContainer;
                allChildren = statusBar.Children;
                text = allChildren(end);
                allStatus = allChildren(1:end-1);
                placements = get(allStatus, 'UserData');
                if iscell(placements)
                    
                    % If any items don't have the placement, set it to 0.
                    placements(cellfun(@isempty, placements)) = {0};
                    placements = [placements{:}];
                end
                [~, i] = sort(placements);
                statusBar.Children = [allStatus(i); text];
                
                statusBar.ResizeFcn(statusBar);
            end
            % Create text control within frame
            hStatusText = uicontrol('Parent', hContainer, ...
                'Style', 'text', ...
                'String', '', ...
                'Enable', obj.Enable, ...
                'ButtonDownFcn', obj.Callback, ...
                'TooltipString', obj.Tooltip, ...
                'Tag', obj.Tag, ...
                'Units', 'pix', ...
                'UIContextMenu', obj.UIContextMenu, ...
                'Position', [2 2 1000 statusHeight-6], ...
                'HorizontalAlignment', 'left');
            obj.TextHandle = hStatusText;
            if nargout > 0
                varargout = {hStatusText};
            end
        end
    end
    
    methods (Access = 'private')
        function applyWidth(obj)
            % Apply width to status objects
            
            hContainer = obj.ContainerHandle;
            if isempty(hContainer)
                return;
            end
            textWidth = obj.Width * obj.PixelFactor;
            
            % Adjust width of text
            pos = get(hContainer, 'Position');
            pos(3) = textWidth;
            set(hContainer, 'Position', pos);
            
            % Set Position on statusbar flow container to trigger ResizeFcn
            statusFlowHandle = get(hContainer, 'Parent');
            if ~matlabshared.scopes.useWebFigure()
                set(statusFlowHandle, 'Position', get(statusFlowHandle, 'Position'));
            end
            
            statusFlowHandle.ResizeFcn(statusFlowHandle);

        end
        
        function applyText(obj)
            % Apply text to text control
                        
            % Set status text
            str = obj.Text;
            hStatusText = obj.TextHandle;
            
            % Create container to hold frame and text
            if isempty(hStatusText)
                if isempty(str)
                    % Nothing to do
                    return
                end
                
                hStatusText = forceRender(obj);
            end
            set(hStatusText, 'String' ,str);
            
            if obj.AutoGrow
                
                % Use AutoGrowCounter to reduce the number of times AutoGrow
                % check is performed (g775153)
                obj.AutoGrowCounter = obj.AutoGrowCounter + 1;
                if ~obj.AutoGrowAllowSkips || obj.AutoGrowCounter >= 2
                    obj.AutoGrowCounter = 0;
                    
                    pixf = obj.PixelFactor;
                    % Get width and extent of text
                    hContainer = obj.ContainerHandle;
                    pos = get(hContainer, 'Position');
                    oldWidth = pos(3)/pixf;
                    
                    if ispc
                        factor = get(hStatusText, 'FontSize');
                    else
                        factor = get(hStatusText, 'FontSize')/8*9.6;
                    end
                    % Most characters take up 1 full width
                    % . 1 f i l t, all take up 1/2 a space.
                    allWidths = [...
                        1.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0  ...
                        0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0  ...
                        0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0  ...
                        0.0 0.4 0.3 0.5 0.8 0.8 1.3 0.9 0.3 0.5  ...
                        0.5 0.5 0.8 0.4 0.5 0.4 0.4 0.8 0.8 0.8  ...
                        0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.4 0.4 0.8  ...
                        0.8 0.8 0.8 1.4 1.0 0.9 0.9 0.9 0.8 0.8  ...
                        1.0 0.9 0.3 0.7 0.9 0.8 1.0 0.9 1.0 0.8  ...
                        1.0 0.9 0.9 0.8 0.9 1.0 1.3 0.9 1.0 0.9  ...
                        0.4 0.4 0.4 0.7 0.8 0.5 0.8 0.8 0.8 0.8  ...
                        0.8 0.5 0.8 0.8 0.3 0.3 0.7 0.3 1.0 0.8  ...
                        0.8 0.8 0.8 0.5 0.8 0.4 0.8 0.8 1.3 0.8  ...
                        0.8 0.8 0.5 0.3 0.5 0.8 0.0 0.0 0.0 0.7  ...
                        0.7 0.7 1.2 0.7 0.7 0.5 1.2 0.7 0.7 1.4  ...
                        0.0 0.0 0.0 0.0 0.3 0.3 0.7 0.7 0.0 0.8  ...
                        1.4 0.5 1.3 0.5 0.4 0.8 0.0 0.0 1.0 0.4  ...
                        0.3 0.8 0.8 0.9 0.8 0.3 0.8 0.5 1.0 0.5  ...
                        0.8 0.8 0.5 1.0 0.8 0.5 0.8 0.5 0.5 0.5  ...
                        0.8 0.8 0.5 0.5 0.5 0.7 0.8 1.2 1.3 1.3  ...
                        0.8 1.0 1.0 1.0 1.0 1.0 1.0 1.4 0.9 0.8  ...
                        0.8 0.8 0.8 0.3 0.3 0.3 0.3 1.0 0.9 1.0  ...
                        1.0 1.0 1.0 1.0 0.8 1.0 0.9 0.9 0.9 0.9  ...
                        1.0 0.9 0.9 0.8 0.8 0.8 0.8 0.8 0.8 1.3  ...
                        0.8 0.8 0.8 0.8 0.8 0.3 0.3 0.3 0.3 0.8  ...
                        0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8  ...
                        0.8 0.8 0.8 0.8 0.8];
                    str = abs(str);
                    try
                        width = factor*sum(allWidths(str));
                    catch ME %#ok
                        % Cannot calculate non-standard ascii
                        ext = get(hStatusText, 'Extent');
                        
                        % Remove the pixel factor here.  It will be applied
                        % later.
                        width = ext(3)/pixf;
                    end
                    candidateWidth = width + 4;
                    allowableEmptySpace = 30;
                    
                    if width > oldWidth+.01
                        
                        % If extent is greater than text uicontrol width, set
                        % Width to either the candidate width or max width.
                        % Note that setting Width will trigger a recursive call
                        % to applyText.
                        
                        if obj.Width < obj.AutoGrowMax
                            if candidateWidth < obj.AutoGrowMax
                                obj.Width = candidateWidth;
                            else
                                obj.Width = obj.AutoGrowMax;
                            end
                        end
                    elseif width < (oldWidth - allowableEmptySpace)
                        
                        % If extent is less than text uicontrol width minus
                        % an allowable buffer amount, set Width to either the
                        % candidate width or min width.  Note that setting
                        % Width will trigger a recursive call to applyText.
                        
                        if obj.Width > obj.AutoGrowMin
                            if candidateWidth > obj.AutoGrowMin
                                obj.Width = candidateWidth;
                            else
                                obj.Width = obj.AutoGrowMin;
                            end
                        end
                    end
                end
            end
        end
    end
end

% [EOF]
