classdef polariMBAngleTicks < internal.polariMouseBehavior
    
    methods
        function obj = polariMBAngleTicks(isButtonDown)
            
            if nargin > 0 && isButtonDown
                obj.InstallFcn = @showToolTipAndPtr_angleTicksDown;
                obj.MotionEvent = @wbdrag;
                obj.DownEvent = [];
                obj.UpEvent = @wbup;
                obj.ScrollEvent = [];
            else
                obj.InstallFcn = @showToolTipAndPtr_angleTicks;
                obj.MotionEvent = @internal.polariMBAngleTicks.wbmotion;
                obj.DownEvent = @wbdown;
                obj.UpEvent = [];
                obj.ScrollEvent = [];
            end
        end
    end
    
    methods (Static)
        function hiliteAngleTickLabelDrag_Init(p,state)
            % Add graphical affordances to the angle axis to indicate:
            %   - mouse drag will change angle offset at top or angle
            %     resolution
            %   - drag in either direction is useful
            %   - highlights and follows cursor position, including labeled
            %     and non-labeled angles
            %
            % state:
            %     'on' - initial highlighting
            %     'off' - remove highlighting
            %     'down' - mouse down
            
            h = p.hAngleTickLabelHilite;
            isValid = ~isempty(h) && ishghandle(h.lr);
            
            is_on = ~strcmpi(state,'off');
            p.pAngleTickLabelHilite = is_on;
            if ~is_on
                % Turn off highlighting
                if isValid
                    set([h.ud h.lr],'Visible','off');
                end
                return % EARLY  EXIT
            end
            
            % Create highlight widgets if not already done
            % 4 arrowheads at N/E/S/W:
            if ~isValid
                x = [-1 0 +1 -1]*.5; % up arrow
                y = [ 0 1  0  0]*.5;
                up = [x;y];
                y = [0 -1 0 0]*.5;   % down arrow
                dn = [x;y];
                x = [0 -1 0 0]*.5;   % left arrow
                y = [1 0 -1 1]*.5;
                lt = [x;y];
                x = [0 1  0 0]*.5;   % right arrow
                rt = [x;y];
                u = {up,dn,lt,rt};
                
                h.ud = patch( ...
                    'Parent',p.hAxes, ...
                    'HandleVisibility','off', ...
                    'FaceColor','none', ...
                    'FaceAlpha',1.0, ...
                    'EdgeAlpha',1.0, ...
                    'Clipping','on', ...
                    'UserData',u, ...
                    'Visible','off');
                h.lr = patch( ...
                    'Parent',p.hAxes, ...
                    'HandleVisibility','off', ...
                    'FaceColor','none', ...
                    'FaceAlpha',1.0, ...
                    'EdgeAlpha',1.0, ...
                    'Clipping','on', ...
                    'UserData',u, ...
                    'Visible','off');
                
                % Disable data cursor from attaching to this
                %b = hggetbehavior(h.ud,'DataCursor');
                %b.Enable = false;
                %b = hggetbehavior(h.lr,'DataCursor');
                %b.Enable = false;
                
                % Attach context menu
                set([h.ud h.lr],'uicontextmenu',p.UIContextMenu_AngleTicks);
                
                p.hAngleTickLabelHilite = h;
            end
            
            % Set colors of arrowheads
            set([h.ud h.lr], ...
                'FaceColor',p.GridBackgroundColor, ...
                'EdgeColor',p.pAngleTickLabelColor);
            %{
            if strcmpi(state,'down')
                % mouse down
                set([h.ud h.lr], ...
                    'FaceColor',p.GridForegroundColor, ...
                    'EdgeColor',p.pAngleTickLabelColor);
            else
                % mouse hover
                set([h.ud h.lr], ...
                    'FaceColor','none', ...
                    'EdgeColor',p.pAngleTickLabelColor);
            end
                %}
                
                % Force highlights visible if not an update call.
                %
                % During updates, we're in an interactive update loop, where
                % i_angle() gets called.
                %
                internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Update(p);
                %overrideAngleTickLabelVis(p,'on');
        end
        
        function hiliteAngleTickLabelDrag_Update(p,closestTextIdx)
            % Update angle-tick highlights.
            
            hpatch = p.hAngleTickLabelHilite;
            if strcmpi(p.pAngleTickLabelHilite,'off') || ...
                    isempty(hpatch) || ~ishghandle(hpatch.lr)
                return
            end
            
            if p.EnableAngleDragRadialDir
                ud_vis_on = 'on';
            else
                ud_vis_on = 'off';
            end
            
            if p.AngleDrag_ChangedMag
                % only show up/down arrows
                hpatch.ud.Visible = ud_vis_on;
                hpatch.lr.Visible = 'off';
            elseif p.AngleDrag_ChangedAngle
                % only show left/right arrows
                hpatch.ud.Visible = 'off';
                hpatch.lr.Visible = 'on';
            elseif p.pAngleTickLabelHilite
                % Show all 4 arrows
                hpatch.ud.Visible = ud_vis_on;
                hpatch.lr.Visible = 'on';
            else
                hpatch.ud.Visible = 'off';
                hpatch.lr.Visible = 'off';
            end
            
            % Where to place the highlight?
            %
            % - use quantized angle position
            % - may be a angle tick label, or MAY NOT!
            %
            % for now, use unquantized position
            
            if nargin<2
                % No index passed
                
                % Find closest angle-text label to the cursor
                %
                % Get mouse coords in polar form
                pt = get(p.hAxes,'CurrentPoint');
                closestTextIdx = findClosestAngleTextLabel(pt(1,1:2), p.hAngleText);
                
            elseif ~isscalar(closestTextIdx)
                % point passed, not index
                %
                % Use that point, not the current mouse, to locate closest
                % angle text label.  It could be changing during interactive
                % change to angle resolution, hence the search.
                orig_pt = closestTextIdx;
                closestTextIdx = findClosestAngleTextLabel(orig_pt(1,1:2), p.hAngleText);
                
                %else
                % closestTextIdx: index passed directly
            end
            p.pClosestAngleTickLabel = closestTextIdx;
            hh = p.hAngleText(closestTextIdx);
            
            % Highlight is 4 arrowheads surrounding angle label text.
            %
            % Get text position and extent:
            %
            % Getting Extent directly hurts interactive performance here,
            % so we use a cached value:
            pos = hh.Position;
            ext = getappdata(hh,'Extent');
            
            % Rotate then offset:
            if p.AngleTickLabelRotation
                % Text is already rotated, just get its angle:
                th = hh.Rotation*pi/180;
            else
                th = atan2(pos(2),pos(1))-pi/2;
            end
            rotccw = [cos(th) -sin(th); sin(th) cos(th)];
            
            % Get parts we need:
            %
            % We need each enclosed patch in one column of x and y.
            % We pad the cols to the max # of vertices of each
            % enclosed region.
            
            % 1: Allocate x and y matrices, to put each part into a
            % column of x and y, padding rows with NaNs
            Nparts = 4;
            x = NaN(4,Nparts);
            y = NaN(4,Nparts);
            
            % Mag-label text extent in "normal orientation"'
            % - get actual text extent to determine x/y proportions
            % Don't let arrow scale exceed text extent
            %rmin = max(0.06, min(ext));
            rmin = min(0.10, min(ext));
            
            % Leave more gap/headroom horizontally vs vertically
            % for a margin between text and arrowheads:
            xe = ext(1)/2 + max(0.02,0.05*ext(1));
            ye = ext(2)/2 + max(0.01,0.03*ext(2));
            
            % 2: get each icon part and rotate it, then put each
            % part into a column of x and y
            u = hpatch.lr.UserData; % or .ud.UserData
            for i = 1:Nparts
                % Uniform arrowhead scaling in x and y:
                u_i = u{i} * rmin;
                
                % Offset arrows by text extent
                % (extent from text in "normal orientation")
                if i==1 % up: incr y
                    u_i(2,:) = u_i(2,:) + ye;
                elseif i==2 % dn: decr y
                    u_i(2,:) = u_i(2,:) - ye;
                elseif i==3 % lt: decr x
                    u_i(1,:) = u_i(1,:) - xe;
                else % rt: incr x
                    u_i(1,:) = u_i(1,:) + xe;
                end
                
                % Rotate and store
                p_i = rotccw * u_i;
                x(:,i) = p_i(1,:)';
                y(:,i) = p_i(2,:)';
            end
            
            % 3: final offset
            x = x + pos(1);
            y = y + pos(2);
            z = y;
            z(~isnan(z)) = 0.294; % z-coord for angle tick hilite
            
            % 4: apply to patches
            set(hpatch.ud, ...
                'XData', x(:,1:2), ...
                'YData', y(:,1:2), ...
                'ZData', z(:,1:2));
            set(hpatch.lr, ...
                'XData', x(:,3:4), ...
                'YData', y(:,3:4), ...
                'ZData', z(:,3:4));
        end
        
        function wbmotion(p,ev)
            
            % Register hover point as current point, so context-menu
            % actions based on current point (such as "rotate to top") will
            % work:
            p.hFigure.CurrentPoint = ev.Point;

            firstEntry = p.ChangedState;
            hOverrideContext = [p.Parent p.hCircles{3}];
            if firstEntry
                % Just entered this new state
                p.ChangedState = false;
                
                % Update context menu for background
                % - from master menu to angleticks menu
                set(hOverrideContext,'uicontextmenu',p.UIContextMenu_AngleTicks);
                
                if p.AngleHiliteOnHover
                    % Make sure angle ticks are visible while mouse hovers over
                    % them, important when tick vis is disabled by user.
                    overrideAngleTickLabelVis(p,'on');
                    
                    % Less noisy display when angle-ticks don't hilite on
                    % mouse-over, just on mouse-down:
                    internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Init(p,'on');
                end
                
                showToolTipAndPtr_angleTicks(p);
            end
            
            % Give priority to hovering over Magnitude Ticks, by explicitly
            % ending angle-tick if mag ticks are also under the cursor
            % (angle ticks and the top part of mag ticks overlap)
            %
            s = computeHoverLocation(p,ev);
            if ~s.inAxes ...
                    || ~s.overAngleTicks ...
                    || s.overMagnitudeTicks ...
                    || s.overMarker
                
                internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Init(p,'off');
                
                if strcmpi(p.AngleTickLabelColorMode,'auto')
                    % Restore tick color when hovering done
                    updateAngleTickLabelColor(p);
                    set(p.hAngleText,'Color',p.pAngleTickLabelColor);
                end
                
                % Return angle tick vis to default state, which may be
                % invisible.
                overrideAngleTickLabelVis(p,'default');
                
                % Update context menu for background
                % - from angleticks menu to master menu
                set(hOverrideContext,'uicontextmenu',p.UIContextMenu_Master);
                
                autoChangeMouseBehavior(p,s);
            else
                % Hovering in the angle tick region
                
                if firstEntry && strcmpi(p.AngleTickLabelColorMode,'auto')
                    % Temporary change in tick color while hovering
                    c = internal.ColorConversion.getBWContrast(p.GridBackgroundColor);
                    p.pAngleTickLabelColor = c;
                    set(p.hAngleText,'Color',c);
                end
                
                % Did we move closer to a different angle tick?
                if p.AngleHiliteOnHover
                    pt = p.hAxes.CurrentPoint;
                    idx = findClosestAngleTextLabel(pt(1,1:2), p.hAngleText);
                    if idx ~= p.pClosestAngleTickLabel
                        internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Update(p,idx);
                    end
                end
            end
        end
    end
end

% Mouse: AngleTicks
%
%   Normal: - quantized rotation of AngleAtTop
%           - magnitude pull: change # of circles/mag ticks
%   Shift or Ctrl: continuous rotation
%

function wbdown(p,ev)

p.hFigure.CurrentPoint = ev.Point;

% Get mag/theta mouse coords, setup cache entries.
% Do before enabling new mouse behaviors:
pt = p.hAxes.CurrentPoint(1,1:2);

st = p.hFigure.SelectionType;
if strcmpi(st,'open')
    datasetIndex = [];  % cursor connects to "Active Trace"
    m = i_addCursor(p,pt,datasetIndex);
    if isempty(m)
        return % no data, so no marker added
    end
    
    % Force update of marker highlight, drag state, etc, with a
    % special 3rd-arg call to wbmotion_*:
    changeMouseBehavior(p,'anglemarker');
    internal.polariMBAngleMarker.wbmotion(p,ev,m); % hover over ...
    % Override SelectionType so wbdown_anglemarker doesn't try
    % to also do a "double-click" action
    p.hFigure.SelectionType = 'normal';
    internal.polariMBAngleMarker.wbdown(p,ev);     % ... then button down
    
    return
elseif strcmpi(st,'alt')
    % ignore right-click
    %
    % If we don't do this early-exit, we will flash the 4-arrow
    % hilites on the current angle as we also bring up the
    % context-menu.  Looks bad.
    resetToolTip(p);
    return
end

p.AngleDrag_StartMagClicked   = hypot(pt(2),pt(1));
p.AngleDrag_StartAngleClicked = atan2(pt(2),pt(1)) * 180/pi;
p.AngleDrag_StartShiftPressed = p.pShiftKeyPressed;
p.AngleDrag_Delta = 0;  % Initialize before drag
p.AngleDrag_ChangedAngle = false;
p.AngleDrag_ChangedMag = false;

% Set the interactive angle-rotation quantization angle to be
% half the angle between angle labels, or 15 degrees, whichever
% is smaller.
if p.pShiftKeyPressed
    % Smooth rotation when shift pressed
    dth = 0;
else
    % Quantized rotation normally
    S = p.pAngleLabelCoords;
    dth = min(15, abs(S.th(2)-S.th(1))*90/pi);
end
p.pAngleRotationStepSize = dth;

% Need to show the angle ticks when clicked, do it now if we
% suppressed showing these on hover:
if ~p.AngleHiliteOnHover
    overrideAngleTickLabelVis(p,'on');
end

internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Init(p,'down');

changeMouseBehavior(p,'angleticks_buttondown');
end

function wbdrag(p,ev)

% Get mouse pointer in local axis reference frame.
%
% For angle rotation:
%   normal drag: quantized angle rotations
%   shift drag: continuous angle rotations
%
%   Compute delta-theta such that ADDING it to AngleAtTop
%   would yield new angle for theta axes.
%
% For magnitude change:
%    normal drag: increase/decrease angle resolution

p.hFigure.CurrentPoint = ev.Point;

pt = get(p.hAxes,'CurrentPoint');
shiftPressed = p.AngleDrag_StartShiftPressed;
mag = hypot(pt(1,2),pt(1,1));
del_mag = mag - p.AngleDrag_StartMagClicked;


firstEntry = p.ChangedState;
if firstEntry
    p.ChangedState = false;
    showToolTipAndPtr_angleTicksDrag(p);
end

if ~shiftPressed && ~p.AngleDrag_ChangedAngle
    % Radial/Magnitude drag on angle tick
    %
    % Preference goes to angle drag
    
    if p.EnableAngleDragRadialDir ...
            && ~p.AngleDrag_ChangedMag ...
            && (abs(del_mag) > 0.1)
        % Overcame a small bit of hysteresis. We are now
        % confident the user intended to drag radially:
        p.AngleDrag_ChangedMag = true;
        
        % Record initial point, so we can "lock in" the angle
        % while dragging radially.
        p.pClosestAngleTickLabel_origpt = pt;
    end
end
if p.AngleDrag_ChangedMag
    
    % Remove the outward cursor motion behavior - Shashank
%     % Show/Hide AngleLim cursors
%     %
%     % If outward radial drag, show AngleLimCursors if hidden.
%     % If inward radial drag, hide AngleLimCursors if shown.
%     if (del_mag > 0) && ~p.pAngleLimCursorVis
%         % Outward radial drag
%         showAngleLimCursors(p,true, ...
%             p.AngleDrag_StartAngleClicked);
%         
%     elseif (del_mag < 0) && p.pAngleLimCursorVis
%         % Inward radial drag
%         showAngleLimCursors(p,false);
%     end
%     
%     % Call this even if no change to offset occurred.
%     % You'd think we'd call this within the "curr_res~=new_res"
%     % condition... but we need to signal to the user that we've
%     % now declared "AngleDrag_ChangedMag", and locked the
%     % movement to mag-only now, regardless of the fact that no
%     % actual change has yet occurred.
%     %
%     % update hilite, locking our hilite to the initial closest
%     % angle (works even if we let it wander, but it's
%     % disconcerting to the user to see different angles being
%     % chosen while dragging radially)
%     internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Update(p,p.pClosestAngleTickLabel_origpt);
    %internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Update(p,p.pClosestAngleTickLabel);
    return
end

% Compute theta rotation
%
th = atan2(pt(1,2),pt(1,1)) * 180/pi;        % normalized polar angle (0=east always)
del_th = th - p.AngleDrag_StartAngleClicked; % subtract normalized angles
if strcmpi(p.AngleDirection,'ccw')
    del_th = -del_th;
end

% See if change in shift-state occurred
if shiftPressed ~= p.pShiftKeyPressed
    % Change angle step-size
    if p.pShiftKeyPressed
        % Smooth rotation when shift pressed
        dth = 0;
    else
        % Quantized rotation normally
        S = p.pAngleLabelCoords;
        dth = min(15, abs(S.th(2)-S.th(1))*90/pi);
    end
    p.pAngleRotationStepSize = dth;
    
    % Update shift-state for change detection:
    p.AngleDrag_StartShiftPressed = p.pShiftKeyPressed;
end

% Impose quantization on angle step-size
stepSize = p.pAngleRotationStepSize;
if stepSize > 0
    del_th = round(del_th/stepSize)*stepSize;
    
    % Add offset to delta that makes up for AngleAtTop
    % not being at a quantized stepSize (if that's the case).
    % Makes it easier for user to get back to exact, quantized
    % steps.
    t = p.AngleAtTop;
    tq = round(t/stepSize)*stepSize;
    if t ~= tq
        del_th = del_th-t+tq;
    end
end
% Clear out if very close to zero
if abs(del_th) < 1e-3
    del_th = 0;
end

% Only mark as changed if non-zero, and past a small threshold
% to prevent work for small "chatter"
if del_th ~= p.AngleDrag_Delta
    p.AngleDrag_Delta = del_th;
    p.AngleDrag_ChangedAngle = true;
    i_changeAngleAtTop(p);
end
end

function wbup(p,ev)

% Set point into axes:
p.hFigure.CurrentPoint = ev.Point;

internal.polariMBAngleTicks.hiliteAngleTickLabelDrag_Init(p,'off');

% Restore tick color in 'auto' mode
if strcmpi(p.AngleTickLabelColorMode,'auto')
    updateAngleTickLabelColor(p);
    set(p.hAngleText,'Color',p.pAngleTickLabelColor);
end

if p.AngleDrag_ChangedAngle
    % Key task: change public (user's) property value
    %
    % Wrap angle into proper range for AngleRange
    th = p.AngleAtTop + p.AngleDrag_Delta;
    if strcmpi(p.AngleRange,'180')
        % Put th in range (-180,+180]
        th = th - floor((th+180)/360)*360;
        if th==-180
            th = 180;
        end
    else
        % Put th in range [0,360)
        th = th - floor(th/360)*360;
    end
end

% Clear out states used during drag (not essential)
p.AngleDrag_StartAngleClicked = []; % normalized unit circle
p.AngleDrag_Delta             = 0;  % *Must* reset to 0

if p.AngleDrag_ChangedAngle
    % Do this *after* _Delta is updated to 0.
    p.AngleAtTop = th;
end

p.AngleDrag_ChangedAngle = false;
p.AngleDrag_ChangedMag = false;

% If mouse remains over angle ticks, keep angle ticks visible.
% This is only a concern in the case where they are normally
% not invisible.
s = computeHoverLocation(p,ev);
if s.overAngleTicks
    % Still over angle ticks after a drag-release operation.
    
    changeMouseBehavior(p,'angleticks');
    
    % Need to restore the vis of angle ticks since we are after
    % a click, if we suppressed showing these on hover.
    % Otherwise they disappear.  We want them to continue to
    % show after a drag-release op.
    if ~p.AngleHiliteOnHover
        overrideAngleTickLabelVis(p,'on');
    end
else
    % Update mouse handlers
    autoChangeMouseBehavior(p,s);
end
end

function showToolTipAndPtr_angleTicks(p)
if p.ToolTips && ~isempty(p.hToolTip) && isvalid(p.hToolTip)
    % Guide how to change the current state:
    if p.pAngleLimCursorVis % currently showing
        str = '';%'Drag inward to hide angle limits'; %Shashank
    else
        str = '';%'Drag outward to show angle limits';%Shashank
    end
    start(p.hToolTip, { ...
        'Drag to rotate angle ticks', str});
end
setptr(p.hFigure,'hand'); % 'rotate'
end

function showToolTipAndPtr_angleTicksDown(p)
if p.ToolTips && ~isempty(p.hToolTip) && isvalid(p.hToolTip)
    % Guide how to change the current state:
    if p.pAngleLimCursorVis % currently showing
        str = '';%'Drag inward to hide angle limits';%Shashank
    else
        str = ''; %'Drag outward to show angle limits';%Shashank
    end
    start(p.hToolTip, { ...
        'Drag to rotate angle ticks', str});
end
setptr(p.hFigure,'closedhand');
end

function showToolTipAndPtr_angleTicksDrag(p)
resetToolTip(p);
end

function idx = findClosestAngleTextLabel(pt,ht)
% pt: point in axes data units, [x y]
%     NOTE: pt may come in as a 2x3 from the axes CurrentPoint
% ht: vector of text handles

ht_pos = cat(1,ht.Position);
dist = bsxfun(@minus,pt(1,1:2),ht_pos(:,1:2));
[~,idx] = min(sum(dist.*dist,2)); % x^2+y^2
%hh = ht(idx); % closest angle text label

end
