classdef polariAntenna < handle
    % Antenna-specific measurements on antenna radiation patterns.
    
    % TODO:
    % 
    % - when display units is 'linear',
    %   still need to convert to dB in measurements
    %
    % - when mag zoom changed, non-nulls may cross null threshold, or vice
    %   versa.  So HPBW and other measures can change.  Need to update
    %   antenna metrics after zooming.  It would be VERY slow to do it
    %   dynamically.  Allow for an "update metrics" menu pick?
    
    
    properties
        hLobes     % lobe polygon structure
        polariObj
        Listeners
        
        % Index of dataset for which LobeInfo is computed.
        % Empty if no LobeInfo
        DatasetComputed = []
        LobeInfo = []
        
        % Markers for visualizing metrics
        Marker1
        Marker2
        
        % True to display antenna lobes as filled patches
        FillLobes = true
    end
    
    properties (SetAccess = private)
        % String specifying the measurement type:
        %   'none' - no measurement span displayed
        %   'hpbw' - half-power beamwidth
        %   'fnbw' - first-null beamwidth
        %   'sll'  - sidelobe level
        %   'fb'   - front/back ratio
        LastSpanType = 'none'
    end
    
    properties (Access=private)
        % True when readout context menu created
        pReadoutContextMenuCreated = false
    end
    
    properties (Constant)
        DeleteMarkersWhenHidingLobes = false
        SymbolDegree = char(176) % degrees, extended ASCII
    end
    
    methods
        function a = polariAntenna(polariObj)
            if nargin > 0
                % No other way to initialize this object.
                %
                % However, we can construct one without polariObj just for
                % debug.
                init(a,polariObj);
            end
        end
        
        function set.FillLobes(a,val)
            validateattributes(val,{'numeric','logical'}, ...
                {'scalar','real'},'polariAntenna','FillLobes');
            
            a.FillLobes = val~=0;            
            updateLobeVis(a,false); % side-effect
        end
        
        function y = areLobesVisible(a,datasetIndex)
            % areLobesVisible(a) is true if lobes are visible for ANY
            % dataset.
            %
            % areLobesVisible(a,datasetIndex) is true if lobes are visible
            % for the specified dataset.
            
            y = ~isempty(a.hLobes);
            if y && (nargin > 1)
                y = isequal(a.DatasetComputed, datasetIndex);
           end
        end
        
        function hideLobes(a)
            % Deletes visualization, retains lobe computations.
            
            % Removes markers created to visualize the angle span for
            % metrics, if option set.
            if a.DeleteMarkersWhenHidingLobes
                deleteSpanMarkers(a);
            end
            deleteLobeVis(a);
        end

        function delete(a)
            % Delete all side-effect info
            deleteListeners(a);
            deleteLobeInfo(a);
        end
        
        function markDirty(a)
            % Leaves visualization, marks lobe info cache as dirty.
            %
            % Next call to showLobes will recompute lobe info and update
            % visual.
            
            a.DatasetComputed = [];
        end
        
        function showLobes(a,datasetIdx)
            %Visualize antenna lobes.
            %   showLobes(P,datasetIdx) displays mainlobe, backlobe and
            %   sidelobe information for an antenna radiation pattern
            %   expressed in dB.  Information is shown for the specified
            %   dataset is computed and visualized.
            %
            %  Cached lobe info is used if just the graphics are being
            %  updated.
            findLobes(a,datasetIdx);
            
            % True flag: force creation of visual if not already present
            updateLobeVis(a,true);
        end
        
        function updateAll(a)
            % Force update to lobe data, etc, if lobes shown.
            %
            % Needed when data units change, etc.
            
            if ~isempty(a.hLobes)
                datasetIdx = a.DatasetComputed;
                if ~isempty(datasetIdx)
                    markDirty(a);
                    showLobes(a,datasetIdx);
                    
                    % xxx Update spans (HPBW, etc) ?  showLobeSpan()
                    % Do we track the handle, so we can update spans?
                    updateLobeSpan(a);
                end
            end
        end

        function showPeaks(a,datasetIndex,Npeaks)
            % Add Npeaks highest peaks
            % Default: 2 peaks, for main lobe and side lobe
            
            if nargin < 3
                Npeaks = 2;
            end
            p = a.polariObj;
            if nargin < 2
                datasetIndex = p.pCurrentDataSetIndex;
            end
            % If no data, datasetIndex is empty.
            % Cannot turn on peaks without data.
            if ~isempty(datasetIndex)
                % Setting the i'th entry is correct, but in the case of
                % multiple traces AND if the current dataset is index 1,
                % this looks like a scalar --- and turns on peaks for ALL
                % traceS! So we manufacture a full vector in every case.
                Nd = getNumDatasets(p);
                pk = zeros(1,Nd);
                pk(datasetIndex) = Npeaks;
                p.Peaks = pk;
            end
        end
        
        function removeLobeMarkers(a)
            
            p = a.polariObj;
            showAngleSpan(p,false);
            removeAngleMarkers(p);
            
            % We are likely to have mouse behavior installed that expects
            % angle span or antenna metrics to be active.  With the angle
            % span and markers gone, we need to reset the mouse.
            changeMouseBehavior(p,'general');
            
            a.LastSpanType = 'none';
        end
        
        function L = findLobes(a,datasetIndex,forceRecompute)
            % FINDLOBES(a,datasetIndex) caches a structure in .LobeInfo
            % defining main, back and side lobes identified in data,
            % assuming polar data corresponding to an antenna directivity
            % pattern expressed in dB.  Caches dataset computed as well.
            %
            % No effect on graphics of any kind here, just computation.
            %
            % FINDLOBES(a) returns lobe structure for the current dataset.
            %
            % .mainLobe
            %    .magnitude
            %    .angle
            %    .index
            %    .extent
            %
            % .backLobe
            %    .magnitude
            %    .angle
            %    .extent
            %
            % .sideLobes
            %    .magnitude
            %    .angle
            %    .index
            %    .extent
            %
            %   .FB: Front-to-Back Ratio, in dB
            %        Maximum of main lobe minus the directivity level
            %        180-degrees from the main lobe maximum.
            %   .FBIdx: 1x2 vector of integer indices at FB points
            %
            %   .SLL: SideLobe Level, in dB
            %        Maximum of main lobe minus the highest side- or back-
            %        lobe level.
            %   .SLLIdx: 1x2 vector of integer indices at SLL points
            %
            %   .FNBW: First-Null Beamwidth, in degrees
            %   .FNBWIdx: 1x2 vector of integer indices at FNBW points
            %
            %   .HPBW: Half-Power Beamwidth, in degrees
            %   .HPBWIdx: 1x2 vector of fractional indices at HPBW points
            
            % All index-pairs are in ascending index order, to within
            % wraparound.  Whether that's CW or CCW is ignored.
            
            p = a.polariObj;
            % Comparison here must work for empty entries:
            if isequal(a.DatasetComputed, datasetIndex) && (nargin<3 || ~forceRecompute)
                % Return cached lobe info
                %
                if nargout > 0
                    L = a.LobeInfo;
                end
            else
                % Compute and cache lobe info
                %
                pdata = getDataset(p,datasetIndex);
                L = internal.measureLobes(pdata.ang, pdata.mag, p.DisplayUnits);
                a.LobeInfo = L;
                
                % Cache after getDataset() returned without error.
                % datasetIndex could be bogus, for example.
                a.DatasetComputed = datasetIndex;
            end
        end
        
        function updateLobeSpan(a)
            % Update lobe spans, if visible, assuming data units of parent
            % have changed.
            
            showLobeSpan(a,a.LastSpanType);
        end
        
         function showLobeSpan(a,mtype,datasetIdx, L1)
            % Show angle span illustrating specified measurement.
            %
            % mtype is a string specifying the measurement type:
            %   'hpbw' - half-power beamwidth
            %   'fnbw' - first-null beamwidth
            %   'sll' - sidelobe level
            %   'fb' - front/back ratio
            %   'bw' - generic beamwidth
            
            % If no data, LobeInfo will be empty
            L = a.LobeInfo;
            if isempty(L) && nargin <= 3
                return
            end
            
            % Cache for potential updates
            a.LastSpanType = mtype;
            
            floating = false;
            switch mtype
                case 'fb'
                    ext = L.FBIdx;
                case 'sll'
                    ext = L.SLLIdx;
                case 'hpbw'
                    ext = L.HPBWIdx;
                    ang = L.HPBWAng;
                    floating = false;
                case 'fnbw'
                    % Most accurate representation is to use the
                    % interpolated angles, not the indices, since floating
                    % cursors require angles:
                    ext = L.FNBWIdx;
                case 'bw'
                    ext = L1.BWIdx;
                    floating = false;
                case 'none'
                    % Update to 'none', meaning shut down
                    %removeLobeMarkers(a);
                    return
            end
            
            % Metric may be missing/empty
            p = a.polariObj;
            if isempty(ext)
                showSpan(p,false);
                return
            end
            
            if nargin < 3
                datasetIdx = p.pCurrentDataSetIndex;
            end
            
            ext = round(ext); % for non-floating
            m1 = a.Marker1;
            if isempty(m1) || ~isvalid(m1)
                m1 = addCursorAllArgs(p,ext(1),datasetIdx);
                a.Marker1 = m1;
            end
            m1.Floating = floating;
            if floating
                m1.LocalAngle = ang(1);
            else
                m1.DataIndex = ext(1);
            end
            
            m2 = a.Marker2;
            if isempty(m2) || ~isvalid(m2)
                m2 = addCursorAllArgs(p,ext(2),datasetIdx);
                a.Marker2 = m2;
            end
            m2.Floating = floating;
            if floating
                m2.LocalAngle = ang(2);
            else
                m2.DataIndex = ext(2);
            end
            
            % polariAngleSpan draws CCW regardless of the AngleDirection
            % flag, so we reverse order when AngleDir is CW.  This keeps
            % the angle span displayed in the intended 'ccw' index order.
            if strcmpi(p.AngleDirection,'ccw')
                c1 = m1.ID;
                c2 = m2.ID;
            else
                c1 = m2.ID;
                c2 = m1.ID;
            end
            showSpan(p,c1,c2);
        end
    end
    
    methods
        function updatePosition(a,newPos)
            % Update position of readout associated with the span
            
            hr = a.hLobes;
            if ~isempty(hr)
                updatePosition(hr.textReadout,newPos);
            end
        end
        
        function hoverOverReadoutChange(a,event)
            % Called when mouse is hovering over readout associated with
            % span (not span itself).
            %
            % Events: 'start', 'stop'
            hr = a.hLobes;
            if ~isempty(hr)
                hoverOverReadoutChange(hr.textReadout,event);
            end
        end
    end
    
    methods (Access=private)
        function init(a,p)
            % Initialize antenna measurement object
            a.polariObj = p;
            
            %deleteLobeInfo(a);
            initListeners(a);
        end
        
        function initListeners(a)
            % Listen to properties in polari
            
            % in case of re-init
            deleteListeners(a);
            
            p = a.polariObj;
            
            % Listen for global data unit changes
            %lis.PolariDataUnits = addlistener(p, ...
            %    'DataUnitsChanged',@(~,~)updateLobeVis(a,false));
            lis.PolariDataUnits = addlistener(p, ...
                'DataUnitsChanged',@(~,~)updateAll(a));
            
            % angle ticks were rotated, or magnitudeLim changed
            lis.PolariAngleProps = addlistener(p, ...
                {'AngleAtTop','AngleDirection','AngleDrag_Delta', ...
                'MagnitudeLim','MagnitudeLimMode'}, ...
                'PostSet',@(~,~)updateLobeVis(a,false));
            
            lis.MagLim = addlistener(p,'MagnitudeLimChanged', ...
                @(~,~)updateLobeVis(a,false));

            % when Polari style property is changed, update z-depth setting
            %lis.Primary.PolariStyle = addlistener(p, ...
            %    'Style','PostSet',@(~,~)changedPolarStyle(a,true));
            
            a.Listeners = lis;
        end

        function deleteListeners(a)
            
            % Delete all listeners in all substructs in any order:
            a.Listeners = internal.polariCommon.deleteListenerStruct(a.Listeners);
        end
        
        function deleteLobeInfo(a)
            % Delete lobe info caches.
            %
            % This also deletes graphical lobes.
            
            deleteLobeVis(a);
            
            % Remove lobe info and mark as dirty:
            a.LobeInfo = [];
            a.DatasetComputed = [];
       end
        
        function deleteLobeVis(a)
            % Remove computed lobe info and graphical overlay.
            % Delete all caches.
            
            % Remove graphical visualization of antenna lobes.
            s = a.hLobes;
            if ~isempty(s)
                delete(s.mainLobe);
                delete(s.backLobe);
                delete(s.sideLobes);
                delete(s.textReadout);
                a.hLobes = [];
                                        
                % Reset context-menu creation flag, enables context menu to
                % be re-created on text readout:
                a.pReadoutContextMenuCreated = false;
            end
        end
        
        function deleteSpanMarkers(a)
            % Delete any markers created to visualize angle span associated
            % with the metrics via m_ShowLobeSpan().
            
            % If either marker is present, assume the span is on and we
            % must turn that off too.
            spanVis = false;
            p = a.polariObj;
            
            m1 = a.Marker1;
            if ~isempty(m1)
                if isvalid(m1)
                    %delete(m1);
                    removeCursors(p,m1.Index);
                    spanVis = true;
                end
                a.Marker1 = [];
            end
            
            m2 = a.Marker2;
            if ~isempty(m2)
                if isvalid(m2)
                    %delete(m2);
                    removeCursors(p,m2.Index);
                    spanVis = true;
                end
                a.Marker2 = [];
            end
            
            if spanVis
                p.Span = false;
            end
        end
        
        function hp = recreateLobePatchesIfDirty(a)
            % Caches patches ONCE if needed.
            % Returns property hLobes as a convenience.
            %
            % Need to call this whenever lobe info changes.
            
            % See if lobe patches are already present
            hp = a.hLobes;
            if ~isempty(hp)
                % Assume if one patch is valid, all are:
                if isvalid(hp.mainLobe)
                    return % EARLY EXIT
                end
                
                % Invalid (deleted) patches
                %
                % Delete patches and recreate below:
                deleteLobeVis(a);
            end
            
            % Confirm lobe info is present
            L = a.LobeInfo;
            assert(~isempty(L))
            
            % Setup for patches
            color.mainLobe  = [.8 .4 .4]; % dull red
            color.sideLobes = [.8 .8 .8]; % light gray
            color.backLobe  = [.6 .6 .6]; % darker gray
            %color.backLobe = color.sideLobes;
            %color.backLobe = [.45 .45 .7]; % dull blue
            faceAlpha = 0.65; % transparency for all lobes
            p = a.polariObj;
            
            ctx = createContextMenus(a);
            
            tag = sprintf('polariLobe%d',p.pAxesIndex);
            
            for i = 1:3
                hp_i = [];
                if i==1
                    % Main lobe
                    e = L.mainLobe.extent;
                    faceColor = color.mainLobe;
                    cm = ctx.mainLobeMenu;
                    typ = 'Main';
                elseif i==2
                    % Back lobe
                    e = L.backLobe.extent;
                    faceColor = color.backLobe;
                    cm = ctx.backLobeMenu;
                    typ = 'Back';
                else
                    % Side lobes
                    e = L.sideLobes.extent;
                    faceColor = color.sideLobes;
                    cm = ctx.sideLobeMenu;
                    typ = 'Side';
                end
                
                % Add a patch for each lobe
                for j = 1:size(e,1)
                    hp_j = patch( ...
                        'Parent',p.hAxes, ...
                        'Tag',[tag typ], ...
                        'FaceColor', faceColor, ...
                        'FaceAlpha', faceAlpha, ...
                        'EdgeColor', 'none');
                    hp_j.Annotation.LegendInformation.IconDisplayStyle = 'off';
                    % Setup context menu
                    set(hp_j,'uicontextmenu',cm);
                    
                    % Disable data cursor from attaching to this
                    b = hggetbehavior(hp_j,'DataCursor');
                    b.Enable = false;
                    b = hggetbehavior(hp_j,'PlotEdit');
                    b.Enable = false;
                    
                    if isempty(hp_i)
                        hp_i = hp_j;
                    else
                        hp_i = [hp_i;hp_j]; %#ok<AGROW>
                    end
                end
                if i==1
                  hp.mainLobe = hp_i;
                elseif i==2
                  hp.backLobe = hp_i;
                else
                  hp.sideLobes = hp_i;
                end
            end
            
            % Create readout to show metrics
            hr = internal.polariReadout(a.polariObj);
            hp.textReadout = hr;
            hr.FontName = 'Courier'; % should be fixed-width
            hr.FontRelSize = -2;     % 2 points smaller than polari
            hr.NormalForegroundColor = 'w';
            hr.TagName = 'polariAntennaReadout';
            hr.ReadoutPositionPriority = 3; % 3rd priority position (assume legend)
            hr.NormalBackgroundColor = color.mainLobe;
            hr.ContextMenuFcn = @(hMenu,ev)updateReadoutContextMenu(a,hMenu);
            hr.Visible = 'on';
            
            % Retain graphics handles so they can be updated later
            a.hLobes = hp;
        end
        
        function updateReadoutContextMenu(a,hMenu)
            % Update antenna readout context menu
            if ~a.pReadoutContextMenuCreated
                a.pReadoutContextMenuCreated = true;
                createContextMenuLobe(a,'readout',hMenu);
            end
        end
        
        function updateMetricsReadout(a)
            % Update text showing metrics in readout.
            
            L = a.LobeInfo;
            if isempty(L)
                return
            end
            hp = a.hLobes;
            ht = hp.textReadout;
            deg = a.SymbolDegree;
            
            if isempty(L.HPBW)
                tHPBW = '-';
            else
                tHPBW = internal.polariCommon.sprintfNumTotalDigits(L.HPBW,3);
            end
            if isempty(L.FNBW)
                tFNBW = '-';
            else
                tFNBW = internal.polariCommon.sprintfNumTotalDigits(L.FNBW,3);
            end
            if isempty(L.FB)
                tFB = '-';
            else
                tFB = internal.polariCommon.sprintfNumTotalDigits(L.FB,3);
            end
            if isempty(L.SLL)
                tSLL = '-';
            else
                tSLL = internal.polariCommon.sprintfNumTotalDigits(L.SLL,3);
            end
            
            mainPeak  = internal.polariCommon.sprintfMaxNumTotalDigits(L.mainLobe.magnitude,3);
            mainAngle = internal.polariCommon.sprintfMaxNumTotalDigits(L.mainLobe.angle,3);
            backPeak  = internal.polariCommon.sprintfMaxNumTotalDigits(L.backLobe.magnitude,3);
            backAngle = internal.polariCommon.sprintfMaxNumTotalDigits(L.backLobe.angle,3);
            
            ht.Text = sprintf([ ...
                'HPBW: %s%c\n', ...
                'FNBW: %s%c\n', ...
                ' F/B: %s dB\n', ...
                ' SLL: %s dB\n', ...
                'Main: %s dB @ %s%c\n', ...
                'Back: %s dB @ %s%c'], ...
                tHPBW,deg, tFNBW,deg, tFB, tSLL, ...
                mainPeak, mainAngle, deg, ...
                backPeak, backAngle, deg);
        end
        
        function updateLobeVis(a,forceVisToAppear)
            %Update the visualization of antenna lobes.
            %   updateLobeVis(P,datasetIdx) displays mainlobe, backlobe and
            %   sidelobe information for an antenna radiation pattern
            %   expressed in dB.  Information is shown for the specified
            %   dataset is computed and visualized.
            %
            %   Cached lobe info is used if just the graphics are being
            %   updated.
            
            % We can get called after vis was turned off.
            %
            % In that case, LobeInfo may be present, but we don't want vis
            % to reappear.
            L = a.LobeInfo;
            if isempty(L)
                return
            end
            
            hp = a.hLobes;
            if isempty(hp) && ~forceVisToAppear
                return
            end
            
            % Create visualization if not already present
            hp = recreateLobePatchesIfDirty(a);
            updateMetricsReadout(a);
            
            fill_lobes = a.FillLobes;
            p = a.polariObj;
            
            if strcmpi(p.Style,'line')
                zplane = 0.06;
            else
                zplane = 0.205;
            end
            pdata = getDataset(p,a.DatasetComputed);
            Npts = numel(pdata.ang);
            
            for i = 1:3
                if i==1
                    % Main lobe
                    e = L.mainLobe.extent;
                    hp_i = hp.mainLobe;
                elseif i==2
                    % Back lobe
                    e = L.backLobe.extent;
                    hp_i = hp.backLobe;
                else
                    % Side lobes
                    e = L.sideLobes.extent;
                    hp_i = hp.sideLobes;
                end
                
                % Add a patch for each lobe
                for j = 1:size(e,1)
                    % Get extent pairs
                    %  - One each for mainlobe and backlobe
                    %  - Multiple for sidelobes
                    e1 = e(j,1);
                    e2 = e(j,2);
                    
                    % Get data points for main lobe
                    if e1 > e2
                        % Out of order - two intervals:
                        r0  = pdata.mag([e1:end 1:e2]);
                        th0 = pdata.ang([e1:end 1:e2]);
                        NptsInLobe = e2-e1+1 + Npts;
                    else
                        % In order - one interval:
                        r0  = pdata.mag(e1:e2);
                        th0 = pdata.ang(e1:e2);
                        NptsInLobe = e2-e1+1;
                    end
                    
                    % Normalize for display
                    r = getNormalizedMag(p,r0);
                    th = getNormalizedAngle(p,th0);
                    
                    % - Append point at origin to complete lobes, only if
                    % lobe doesn't encompass the whole dataset (which can
                    % happen for mainlobe of an isotropic radiator)
                    if NptsInLobe < Npts
                        r = [r;0]; %#ok<AGROW>
                        th = [th; th(end)]; %#ok<AGROW>
                    end
                    
                    % Clamp lower limit of radial distance.
                    %
                    % Filling with eps causes drop-outs in the patch.
                    %
                    % Do NOT go to zero for theta, as the filled-patch no
                    % longer understands direction and will not fill
                    % properly.
                    r(r<=0) = 0;
                    
                    x = r.*cos(th);
                    y = r.*sin(th);
                    z = zplane * ones(size(x));
                    
                    set(hp_i(j), ...
                        'XData', x, ...
                        'YData', y, ...
                        'ZData', z);
                end
                
                % Show filled patch only if enabled
                set(hp_i,'Visible',internal.LogicalToOnOff(fill_lobes));
                
                if i==1
                  hp.mainLobe = hp_i;
                elseif i==2
                  hp.backLobe = hp_i;
                else
                  hp.sideLobes = hp_i;
                end
            end
        end
        
        function ctx = createContextMenus(a)
            % Context Menus
            ctx.mainLobeMenu = createContextMenuLobe(a,'main');
            ctx.backLobeMenu = createContextMenuLobe(a,'back');
            ctx.sideLobeMenu = createContextMenuLobe(a,'side');
        end
        
        function hc = createContextMenuLobe(a,typ,hc)
            % Create context menu for one antenna lobe.
            
            p = a.polariObj;
            if nargin < 3
                hc = uicontextmenu( ...
                    'Parent',p.Parent, ...
                    'HandleVisibility','off');
            end
            
            switch typ
                case 'main'
                    dTitle = 'MAIN LOBE';
                    i = 1+2+4;
                case 'back'
                    dTitle = 'BACK LOBE';
                    %i = 2+4;
                    i = 1+2+4;
                case 'side'
                    dTitle = 'SIDE LOBE';
                    %i = 4;
                    i = 1+2+4;
                case 'readout'
                    dTitle = 'ANTENNA METRICS';
                    i = 1+2+4;
            end
            str2 = sprintf('DATASET %d',a.DatasetComputed);
            label = ['<html><b>' dTitle '</b><br>' ...
                '<font size=3><i>' str2 '</i></font></html>'];
            opts = {hc,label,'','Enable','off'};
            internal.ContextMenus.createContext(opts);
            
            sep = true;
            if bitand(i,1)
                internal.ContextMenus.createContext( ...
                    {hc,'Half-Power Beamwidth', @(~,~)m_ShowLobeSpan(a,'hpbw'), ...
                    'separator',internal.LogicalToOnOff(sep)});
                internal.ContextMenus.createContext( ...
                    {hc,'First-Null Beamwidth', @(~,~)m_ShowLobeSpan(a,'fnbw')});
                if sep, sep=false; end
            end
            if bitand(i,2)
                internal.ContextMenus.createContext( ...
                    {hc,'Front-Back Ratio', @(~,~)m_ShowLobeSpan(a,'fb'), ...
                    'separator',internal.LogicalToOnOff(sep)});
                if sep, sep=false; end
            end
            if bitand(i,4)
                internal.ContextMenus.createContext( ...
                    {hc,'Side Lobe Level', @(~,~)m_ShowLobeSpan(a,'sll'), ...
                    'separator',internal.LogicalToOnOff(sep)});
            end
            
            % General trailer
            %  - Remove Metrics
            %  - Export Metrics
            
            %h1 = internal.ContextMenus.createContext({hc, ...
            %    'Antenna Metrics',@(~,~)m_ToggleAntennaMetrics(p), ...
            %    'separator','on'});
            h1 = internal.ContextMenus.createContext({hc, ...
                'Antenna Metrics',@(~,~)m_Remove(a,true), ...
                'separator','on'});
            h1.Checked = 'on';
            internal.ContextMenus.createContext({hc, ...
                'Export Metrics',@(~,~)m_Export(a)});
        end
        
        function m_Export(a)
            %Export metrics variable to the base workspace.
            
            p = a.polariObj;
            
            % Only copy relevant fields
            L = a.LobeInfo;
            E.Dataset = a.DatasetComputed;
            E.HPBW = L.HPBW;
            E.FNBW = L.FNBW;
            E.FB = L.FB;
            E.SLL = L.SLL;
            E.Main = L.mainLobe.magnitude;
            E.Back = L.backLobe.magnitude;
            E.Cursors = p.CursorMarkers;
            E.Peaks = p.PeakMarkers;
            assignin('base','metrics',E);
            str = 'Exported <a href="matlab:eval(''metrics'')">metrics</a> to the base workspace.';
            disp(str);
            
            if p.ToolTips
                showBannerMessage(p, ...
                    'Exported ''metrics'' variable to the base workspace.');
            end
        end
        
        function m_Remove(a,alsoRemoveMarkers)
            % Turn off Antenna Metrics in polari
            
            if alsoRemoveMarkers
                removeLobeMarkers(a);
            end
            hideLobes(a);
        end
        
        function m_ShowLobeSpan(a,mtype)
            % Callback from context menu to display angle span
            % corresponding to specified lobe.
            showLobeSpan(a,mtype);
        end
    end
end

% [EOF]
