function Status = trackpz(Editor,event)
%TRACKPZ  Tracks compensator pole or zero during mouse drag.
%
%   See also SISOTOOL.

%   Author(s): P. Gahinet
%   Copyright 1986-2000 The MathWorks, Inc. 
%   $Revision: 1.8 $  $Date: 2000/08/16 18:15:35 $

persistent MovedGroup PZID Ts Format TransAction
persistent iEditAxes FreqInit Yinit G0 FreqFactor
persistent AutoScaleX AutoScaleY FreqLims Ylims 

HG = Editor.HG;

% Process event
switch event 
   
case 'init'
    % Initialization for compensator pole/zero drag
    Ts = getTs(Editor.LoopData);
    Format = getformat(Editor.LoopData);
    FreqFactor = unitconv(1,Editor.FrequencyUnits,'rad/sec');
    
    % Identify selected axes and selected root
    iEditAxes = find(HG.PlotAxes==gca);
    [MovedGroup,PZID,G0] = LocalGetSelection(gcbo);
    
    % Get initial mag/phase data and remove contribution of moved roots 
    FreqInit = Editor.Frequency;   % rad/sec
    if iEditAxes==1
        Yinit = Editor.Magnitude * abs(getgain(Editor.LoopData));  % absolute
    else
        Yinit = (pi/180) * Editor.Phase;   % radians
    end
    Yinit = LocalCancelRoot(iEditAxes,FreqInit,Yinit,MovedGroup,PZID,Ts,Format);
    FreqInit = FreqInit/FreqFactor;     % initial freqs in current units
    
    % Initialize parameters used to adjust limits/pointer position
    % REVISIT: merge next two
    AutoScaleX = strcmp(Editor.XlimMode,'auto');
    AutoScaleY = strcmp(Editor.YlimMode{iEditAxes},'auto');
    if AutoScaleX | AutoScaleY,
        relocptr('init',HG.PlotAxes(iEditAxes))
        FreqLims = get(HG.PlotAxes(1),'Xlim');
        Ylims = get(HG.PlotAxes(iEditAxes),'Ylim'); 
    end
    
    % Track root location in status bar 
    Status = LocalTrackStatus(MovedGroup,PZID,Ts,Editor.FrequencyUnits);
    
    % Broadcast MOVEPZ:init event
    % RE: Sets RefreshMode='quick' and installs listeners on Pole/Zero property of selected group
    Editor.up.send('MovePZ',...
        sisodata.dataevent(Editor.up,'MovePZ',{'init',MovedGroup}));
    
    % Start transaction
    TransAction = handle.transaction(get(findpackage('sisodata'),'DefaultDatabase'));
    set(TransAction,'Name',sprintf('Move %s',PZID),'OperationStore','on',...
        'InverseOperationStore','on','Compression','on');
    
case 'acquire'
    % Acquire new pole/zero position. 
    % RE: Restrict X position to be in freq. range
    CP = get(HG.PlotAxes(iEditAxes),'CurrentPoint');
    X = max(FreqInit(1),min(CP(1,1),FreqInit(end)));  
    Y = CP(1,2);
    
    % Clear open loop model (stale)
    Editor.LoopData.OpenLoop = [];
    
    % Update pole/zero data for moved group
    % RE: Input of MOVE_* must be in (rad/sec,abs,rad)
    if iEditAxes==1
        Y0 = interpmag(Editor,FreqInit,Yinit,X);   % initial mag value at w=X (in abs)
        Y = unitconv(Y,Editor.MagnitudeUnits,'abs');
        Y = movepzmag(Editor,MovedGroup,PZID,FreqFactor*X,Y,Ts,G0,Y0,Format);
        Y = unitconv(Y,'abs',Editor.MagnitudeUnits);
    else
        Y0 = interp1(FreqInit,Yinit,X);            % initial phase at w=X (in rad)
        Y = unitconv(Y,Editor.PhaseUnits,'rad');
        Y = movepzphase(Editor,MovedGroup,PZID,FreqFactor*X,Y,Ts,G0,Y0,Format);
        Y = unitconv(Y,'rad',Editor.PhaseUnits);
    end
    
    % Broadcast PZDataChanged event (triggers plot updates)
    MovedGroup.send('PZDataChanged',...
        handle.EventData(MovedGroup,'PZDataChanged'));
    
    % Track root location in status bar 
    Status = LocalTrackStatus(MovedGroup,PZID,Ts,Editor.FrequencyUnits);
    
    % Adjust axis limits if dragged pole/zero gets out of focus
    if AutoScaleX | AutoScaleY,
        % Check vertical limits
        [FreqLims,MovePtr1] = LocalCheckLims(Editor,0,FreqLims,X,AutoScaleX);
        [Ylims,MovePtr2] = LocalCheckLims(Editor,iEditAxes,Ylims,Y,AutoScaleY);
        if MovePtr1 | MovePtr2,
            relocptr('move',X,Y,[FreqLims,Ylims])
        end
    end
    
case 'finish'
    % Button up event.   
    Status = '';  % ignored
    
    % Commit and stack transaction
    register(Editor.LoopData,TransAction);
    TransAction = [];   % release persistent objects
    
    % Broadcast MOVEPZ:finish event (exit RefreshMode=quick,...)  
    Editor.up.send('MovePZ',...
        sisodata.dataevent(Editor.up,'MovePZ',{'finish'}));
    
    % Update status and command history
    Str = movelog(MovedGroup,PZID,Ts);
    Editor.up.send('StatusChanged',...
        sisodata.dataevent(Editor.up,'StatusChanged',...
        {sprintf('%s\nRight-click on plots for more design options.',Str),Str}));
    MovedGroup = [];   % release persistent objects
    
    % Trigger global update
    dataevent(Editor.LoopData,'all');
    
end


%----------------- Local functions -----------------

%%%%%%%%%%%%%%%%%%%%%
% LocalGetSelection %
%%%%%%%%%%%%%%%%%%%%%
function [MovedGroup,PZID,G0] = LocalGetSelection(CurrentObj)
% Identifies selected PZGROUP object (pole/zero group)

% Moved PZVIEW object
MovedPZVIEW = get(CurrentObj,'UserData');
if any(MovedPZVIEW.Zero==CurrentObj)
    PZID = 'Zero';
else
    PZID = 'Pole';
end

% Moved PZGROUP
MovedGroup = MovedPZVIEW.GroupData;
G0 = get(MovedGroup);   % initial pole/zero values


%%%%%%%%%%%%%%%%%%%
% LocalCancelRoot %
%%%%%%%%%%%%%%%%%%%
function Y = LocalCancelRoot(MagPhase,Freqs,Y,PZGroup,PZID,Ts,Format)
%  Remove contribution of selected root or groups of roots from magnitude or phase data

%  RE: Convert DT roots to continous time (matched pole/zero conversion).
%      This is warranted by tractability for complex roots and the fact
%      that distorsions will be corrected by REFRESHPZ

s = 1i*Freqs;

% Guard against case z=-1 (only real neg root with |log(z)/Ts|<=pi/Ts)
if any(strcmp(PZGroup.Type,{'Real','LeadLag'})) & ...
        ~isreal(dom2ct(get(PZGroup,PZID),Ts))
    return
end
    
% Get zero and pole values (in CT domain)
rz = dom2ct(PZGroup.Zero,Ts);  % zeros
rp = dom2ct(PZGroup.Pole,Ts);  % poles

% Evaluate root contribution  
resp = 1;
switch lower(Format(1))
case 'z'   % zero/pole/gain
   for ct=1:length(rz)
      resp = resp .* (s - rz(ct));
   end
   for ct=1:length(rp)
      resp = resp ./ (s - rp(ct));
   end
case 't'   % time constant
   for ct=1:length(rz)
      resp = resp .* (1 - s/rz(ct));
   end
   for ct=1:length(rp)
      resp = resp ./ (1 - s/rp(ct));
   end
end

% Remove contribution of moved root(s) from Y data
switch MagPhase
case 1  % mag
    Y = Y ./ abs(resp);
case 2  % phase
    Y = Y - atan2(imag(resp),real(resp));
end


%%%%%%%%%%%%%%%%%%%%
% LocalTrackStatus %
%%%%%%%%%%%%%%%%%%%%
function Status = LocalTrackStatus(Group,PZID,Ts,FreqUnits)
% Display info about moved pole/zero

% Defs
Spacing = blanks(5);

switch Group.Type
case 'Notch'
    % Custom display for notch filters
    Text = 'Drag this compensator notch to adjust its depth and natural frequency.';
    [Wn,Zeta] = damp([Group.Zero(1);Group.Pole(1)],Ts);
    Wn = unitconv(Wn,'rad/sec',FreqUnits);
    Status = ...
        sprintf('%s\nNatural Frequency: %0.3g %s%sZero Damping: %0.3g%sPole Damping: %0.3g',...
        Text,Wn(1),FreqUnits,Spacing,Zeta(1),Spacing,Zeta(2));
    
case 'Complex'
    % Complex pair
    Text = sprintf('Drag this compensator %s to the desired location.',lower(PZID));
    R = get(Group,PZID);
    R = R(1);
    [Wn,Zeta] = damp(R,Ts);
    Wn = unitconv(Wn,'rad/sec',FreqUnits);
    Status = ...
        sprintf('%s\nCurrent location: %0.3g %s %0.3gi%sDamping: %0.3g%sNatural Frequency: %0.3g %s',...
        Text,real(R),char(177),abs(imag(R)),Spacing,Zeta,Spacing,Wn,FreqUnits);
    
otherwise
    % Real pole/zero
    Text = sprintf('Drag this compensator %s to the desired location.',lower(PZID));
    R = get(Group,PZID);
    if Ts
        Wn = unitconv(damp(R,Ts),'rad/sec',FreqUnits);
        Status = sprintf('%s\nCurrent location: %0.3g%sNatural Frequency: %0.3g %s',...
            Text,R,Spacing,Wn,FreqUnits);
    else
        Status = sprintf('%s\nCurrent location: %0.3g',Text,R);
    end
end


%%%%%%%%%%%%%%%%%%
% LocalCheckLims %
%%%%%%%%%%%%%%%%%%
function [Lims,MovePtr] = LocalCheckLims(Editor,Axis,Lims,Location,AutoScale)
% Expands axis limits if specified LOCATION is beyond current limits.
MovePtr = (AutoScale & (Location<Lims(1) | Location>Lims(2)));

if MovePtr
   % Specified location is out of focus
   HG = Editor.HG;
   switch Axis
   case 0  % frequency axis
      % Frequency axis: add a decade to the left or right
      Lims(1) = Lims(1)/10^(Location<Lims(1));   
      Lims(2) = Lims(2)*10^(Location>Lims(2));   
      set(HG.PlotAxes,'Xlim',Lims)
      
   case 1  % mag axis
      % Mag axis: add 40 dB up, 20 dB down
      if strcmpi(Editor.MagnitudeUnits,'dB')
          Lims(1) = Lims(1) - 20 * (Location<Lims(1));
          Lims(2) = Lims(2) + 40 * (Location>Lims(2));
      else
          Lims(1) = Lims(1) / 10^(Location<Lims(1));
          Lims(2) = Lims(2) * 10^(Location>Lims(2));
      end
      set(HG.PlotAxes(1),'Ylim',Lims)
      set(HG.NyquistLine(1,:),'Ydata',Lims);
      
   case 2  % phase axis
      % Phase: add 90 degree up or down
      if strcmpi(Editor.PhaseUnits,'deg')
          Lims(1) = Lims(1) - 90 * (Location<Lims(1));
          Lims(2) = Lims(2) + 90 * (Location>Lims(2));
      else      
          Lims(1) = Lims(1) - 2 * (Location<Lims(1));
          Lims(2) = Lims(2) + 2 * (Location>Lims(2));
      end
      PhaseAxes = HG.PlotAxes(2);
      set(PhaseAxes,'Ylim',Lims)
      set(HG.NyquistLine(2,:),'Ydata',Lims);
      
      % Reset phase ticks
      set(PhaseAxes,'YtickMode','auto')
      if strcmpi(Editor.PhaseUnits,'deg')
          NewTicks = phaseticks(get(PhaseAxes,'YTick'),Lims);
          set(PhaseAxes,'YTick',NewTicks)
      end
   end
end


%%%%%%%%%%
% DOM2CT %
%%%%%%%%%%
function r = dom2ct(r,Ts)
% Returns equivalent continuous-time root
if Ts
    r = log(r)/Ts;
end
