% classdef CoachingTips < handle
%     % Puts a banner message at the top of a figure window.
%     %
%     % Supports time delay to display message, including restarting of timer
%     % to continually defer message display.
%     %
%     % Supports reminder, including restarting of timer to continually defer
%     % reminder.
%     %
%     % Supports time delay to remove message.
%     
%     properties (AbortSet)
%         BackgroundColor = [253 231 65]./255
%         FontSize = 10
%         ForegroundColor = [82 31 22]./255
%         Location = 'bottom'
%         Parent
%         String = 'Coaching Tips';
%         
%         ShowAfter = 0     % String becomes visible after ShowAfter seconds
%         RemainFor = inf   % String gets hidden after RemainFor seconds
%         RemindAfter = inf % String disappears for RemindAfter seconds after sleep
%     end
%     
%     properties (Access = private)
%         pHeight % in pixels
%         pExtent
%         hListeners
%         hBanner
%         hShowAfterTimer
%         hRemainForTimer
%         
%         % States:
%         %   0=stopped/vis
%         %   1=ShowAfter timer running
%         %   2=ShowAfter finished, RemainFor timer running
%         %   3=RemainFor finished, RemindAfter timer running
%         %   4=RemindAfter finished
%         pState = 0
%         
%         % Flag supporting start option that suppresses subsequent calls to
%         % hide(), until the next call to start:
%         pIgnoreHide = false
%     end
%     
%     methods
%         function obj = CoachingTips(parent)
%             
%             if nargin < 1
%                 parent = gcf;
%             end
%             obj.Parent = parent;
%             init(obj);
%         end
%         
%         function delete(obj)
%             
%             deleteListeners(obj);
%             deleteWidgets(obj);
%             deleteTimers(obj);
%         end
%         
%         function hide(obj,isForce)
%             % Make String invisible immediately.
%             
%             % Don't hide text if pIgnoreHide, unless 'force' flag is
%             % passed.  That keeps "long lasting info" on screen until next
%             % start.
%             if ~obj.pIgnoreHide || (nargin>1 && isForce)
%                 stopAllTimers(obj);
%                 hideBanner(obj);
%                 obj.pState = 0; % Enter Stopped state
%             end
%         end
%         
%         function start(obj,str,ignoreHide)
%             % Show string after ShowAfter delay.
%             % Optional input sets new string.
%             % Repeated calls restart the timer.
% 
%             % Process option: ignore hide until next call to start
%             if (nargin > 2) && strcmpi(ignoreHide,'IgnoreHide');
%                 obj.pIgnoreHide = true;
%             elseif obj.pIgnoreHide
%                 % It's the next call to start and no new ignoreHide flag
%                 % was passed:
%                 obj.pIgnoreHide = false;
%             end
%             
%             stopAllTimers(obj);
%             
%             % Don't hide banner if ShowAfter=0, since that
%             % would cause flash if banner currently shown,
%             % and won't matter if banner currently not shown.
%             d = obj.ShowAfter;
%             if d > 0
%                 hideBanner(obj);
%             end        
%             
%             if nargin > 1
%                 obj.String = str;
%             end
%             
%             % User called, we could have been in any state.
%             % Start ShowAfter timer at state 1.
%             obj.pState = 1; % Initial hide state before ShowAfter completes
%             t = obj.hShowAfterTimer;
%             t.Duration = d;
%             start(t);
%         end
%         
%         function sleep(obj)
%             % Resets timer if ShowAfter or RemindAfter timers are running.
%             
%             if obj.pState == 1  % ShowAfter timer running?
%                 % Method on TimeoutTimer, *not* on CoachingTips
%                 start(obj.hShowAfterTimer);
%                 
%             elseif obj.pState == 2 % RemainFor
%                 % no restarting/resetting during this state
%                 
%             elseif obj.pState == 3   % RemindAfter state?
%                 % Hide message if visible.
%                 hideBanner(obj);
%                 
%                 % Restart timer but with RemindAfter delay.
%                 % Method on TimeoutTimer, *not* on CoachingTips
%                 t = obj.hShowAfterTimer;
%                 t.Duration = obj.RemindAfter;
%                 start(t);
%             end
%         end
%         
%         function set.String(obj,val)
%             validateattributes(val,{'char'},{}, ...
%                 'CoachingTips','String');
%             obj.String = val;
%             updateString(obj); % side-effect
%         end
%         
%         function set.FontSize(obj,val)
%             validateattributes(val,{'double'}, ...
%                 {'real','scalar','>',0}, 'CoachingTips','FontSize');
%             obj.FontSize = val;
%             updateFontSize(obj);
%         end
%         
%         function set.Location(obj,val)
%             obj.Location = validatestring(val,{'Top','Bottom'}, ...
%                 'CoachingTips','Location');
%         end
%         
%         function set.ShowAfter(obj,val)
%             validateattributes(val,{'double'}, ...
%                 {'real','scalar','nonnegative'}, ...
%                 'CoachingTips','ShowAfter');
%             obj.ShowAfter = val;
%         end
% 
%         function set.RemainFor(obj,val)
%             validateattributes(val,{'double'}, ...
%                 {'real','scalar','nonnegative'}, ...
%                 'CoachingTips','RemainFor');
%             obj.RemainFor = val;
%         end
%         
%         function set.RemindAfter(obj,val)
%             validateattributes(val,{'double'}, ...
%                 {'real','scalar','nonnegative'}, ...
%                 'CoachingTips','RemindAfter');
%             obj.RemindAfter = val;
%         end
%         
%         function set.ForegroundColor(obj,val)
%             if ischar(val)
%                 validateattributes(val,{'char'},{'row'}, ...
%                     'CoachingTips','ForegroundColor');
%             else
%                 validateattributes(val,{'numeric'}, ...
%                     {'real','size',[1 3]}, ...
%                     'CoachingTips','ForegroundColor');
%             end
%             obj.ForegroundColor = val;
%             updateColor(obj);
%         end
%         
%         function set.BackgroundColor(obj,val)
%             validateattributes(val,{'char'},{'row'}, ...
%                 'CoachingTips','ForegroundColor');
%             obj.BackgroundColor = val;
%             updateColor(obj);
%         end
%     end
%     
%     methods (Access = private)
%         function init(obj)
%             initWidgets(obj);
%             initTimers(obj);
%             initListeners(obj);
%             
%             obj.pState = 0; % Reset to Stopped state
%         end
%         
%         function hideBanner(obj)
%             if ishghandle(obj.hBanner)
%                 obj.hBanner.Visible = 'off';
%             end
%         end
%         
%         function showBanner(obj)
%             if ishghandle(obj.hBanner)
%                 obj.hBanner.Visible = 'on';
%             end
%         end
%         
%         function deleteTimers(obj)
%             % Destroy timers
%             stopAllTimers(obj);
%             delete(obj.hShowAfterTimer);
%             delete(obj.hRemainForTimer);
%         end
%         
%         function stopAllTimers(obj)
%             % Stops all timers.
%             % Returns state prior to stop.
%             
%             % Shut down the ShowAfter timer first, as we don't want it to
%             % complete and "flash up" the text if it's not already shown.
%             if isvalid(obj.hShowAfterTimer)
%                 stopAndWait(obj.hShowAfterTimer);
%             end
%             if isvalid(obj.hRemainForTimer)
%                 stopAndWait(obj.hRemainForTimer);
%             end
%         end
%         
%         function initTimers(obj)
%             obj.hShowAfterTimer = internal.TimeoutTimer( ...
%                 @(~,~)showAfterTimeoutFcn(obj), obj.ShowAfter);
%             obj.hRemainForTimer = internal.TimeoutTimer( ...
%                 @(~,~)remainForTimeoutFcn(obj), obj.RemainFor);
%         end
%         
%         function showAfterTimeoutFcn(obj)
%             % ShowAfter timer expired.
%             % Show coaching tip.
%             % Start RemainFor timer.
%             
%             showBanner(obj);
%             
%             if obj.pState == 1
%                 % Only transition if in the 1 state.
%                 %
%                 % If in state 3, leave as-is --- gives us instant-hide
%                 % capability for "reminder" state after a full show/remain
%                 % cycle has finished.
%                 
%                 obj.pState = 2; % message shown, ShowAfter completed
%             end
%             
%             % If RemainFor is inf, no timer needed, and no remindAfter ever
%             % gets invoked.
%             d = obj.RemainFor;
%             if ~isinf(d)
%                 % Start RemainFor timer:
%                 t = obj.hRemainForTimer;
%                 t.Duration = d;
%                 start(t);
%             end
%         end
%         
%         function remainForTimeoutFcn(obj)
%             % RemainFor timer expired.
%             % Hide coaching tip.
%             % Start RemindAfter timer.
%             
%             hideBanner(obj);
%             obj.pState = 3; % message hidden, RemainFor finished
%         end
%         
%         function deleteWidgets(obj)
%             
%             h = obj.hBanner;
%             if ~isempty(h) && ishghandle(h)
%                 delete(h);
%                 obj.hBanner = [];
%             end
%         end
%         
%         function initWidgets(obj)
%             
%             obj.hBanner = uicontrol( ...
%                 'Parent',obj.Parent, ...
%                 'Style','text', ...
%                 'BackgroundColor',obj.BackgroundColor, ...
%                 'ForegroundColor',obj.ForegroundColor, ...
%                 'HandleVisibility','off', ...
%                 'HorizontalAlignment','left', ...
%                 'FontSize',obj.FontSize, ...
%                 'Units','pixels', ...
%                 'Visible','off');
%             
%             updateString(obj); % formats the string
%             updateFontSize(obj);
%         end
%         
%         function initListeners(obj)
%             lis.Resize = addlistener(obj.Parent,'SizeChanged',@(~,~)resize(obj));
%             obj.hListeners = lis;
%         end
%         
%         function deleteListeners(p)
%             % Delete all listeners
%             try %#ok<TRYNC>
%                 % can't use "if isvalid(p)", since we're likely within a
%                 % delete() call and isvalid() is false by definition.
%                 p.hListeners = internal.polariCommon.deleteListenerStruct(p.hListeners);
%             end
%         end
%         
%         function resize(obj)
%             h = obj.pHeight;
%             if isempty(h)
%                 warning('Height not set');
%                 return
%             end
%             p = obj.Parent;
%             orig = p.Units;
%             p.Units = 'pixels';
%             ppos = p.Position;
%             p.Units = orig;
%             if strcmpi(obj.Location,'top')
%                 pos = [1 ppos(4)-h+1 ppos(3) h];
%             else
%                 pos = [1 1 ppos(3) h];
%             end
%             obj.hBanner.Position = pos;
%             
%             % In case figure width, string length or font size now
%             % overflows width:
%             updateString(obj);
%         end
%         
%         function updateColor(obj)
%             obj.hBanner.ForegroundColor = obj.ForegroundColor;
%             obj.hBanner.BackgroundColor = obj.BackgroundColor;
%         end
%         
%         function ext = setExtent(obj)
%             % Expensive operation
%             ext = obj.hBanner.Extent;
%             obj.pExtent = ext;
%         end
%         
%         function ext = getExtent(obj)
%             ext = obj.pExtent;
%             if isempty(ext)
%                 ext = setExtent(obj);
%             end
%         end
%         
%         function updateFontSize(obj)
%             
%             h = obj.hBanner;
%             h.FontSize = obj.FontSize;
%             ext = setExtent(obj);
%             obj.pHeight = ext(4);
%             
%             resize(obj); % Update banner height
%         end
%         
%         function updateString(obj)
%             % Update string
%             h = obj.hBanner;
%             str = [' ' obj.String];
%             h.String = str;
%             
%             % check width
%             ext = setExtent(obj);
%             dx_text = ext(3);
%             dx_banner = h.Position(3);
%             if dx_text >= dx_banner
%                 %fprintf('dx: text=%d, banner=%d\n', dx_text, dx_banner);
%                 Ns = numel(str);
%                 if Ns > 6
%                     % remove 3 chars to be replaced by '...', may not shorten
%                     % it but now we can add the suppression chars
%                     str = [str(1:end-3) '...'];
%                     while numel(str) > 5 % '...' plus 3 chars to remove
%                         % remove 3 chars each time
%                         str(end-5:end-3) = '';
%                         h.String = str;
%                         ext = setExtent(obj);
%                         if ext(3) < dx_banner
%                             return
%                         end
%                     end
%                 end
%                 h.String = '';
%             end
%         end
%     end
% end
