function varargout = linescan(varargin);
%LINESCAN  Interactively scan lines to display data values, etc.
%
%   LINESCAN as the 'ButtonDownFcn' of a line will handle the display
%   and update of a marker which can be used to show data values and
%   line information as a function of dynamic pointer location.
%
%   LINESCAN(LINE,TRACKMODE,INTERP,STRINGSKEL,STRINGDATA) will activate
%   linescan with the following options:
%
%      LINE:       handle of line to be scanned.
%      TRACKMODE:  'x'  - use pointer x-position to find nearest data point
%                  'xy' - use pointer x- and y-pos " "      "     "     "
%      INTERP:     0    - find nearest real data point (no interpolation)
%                  1    - find nearest interpolated data point
%      STRINGSKEL: String containing C language conversion specifications.
%                  Conversion specifications involving the character % rely
%                  on the STRINGDATA arguments for conversion data.  This
%                  string can either be 1) a cell array, or 2) a single
%                  vector with carriage returns specified by '\n'.
%      STRINGDATA: Any number of data vectors (of same length) to be used
%                  in the conversion of STRINGSKEL.  There should be as many
%                  arguments associated with STRINGDATA as there are
%                  conversion specifications involving the character %.
%
%   If the input argument STRINGSKEL is empty, LINESCAN will allow an
%   external function build the display string.  Under these circumstances,
%   the handle to an external function must be specified in lieu of 
%   STRINGSKEL and STRINGDATA, as follows:
%
%   LINESCAN(LINE,TRACKMODE,INTERP,FUNCTIONHANDLE) or
%   LINESCAN(LINE,TRACKMODE,INTERP,{FUNCTIONHANDLE,FUNCTIONDATA})
%    where the additional input arguments represent:
%
%      FUNCTIONHANDLE: Handle to external function used to build string.
%      FUNCTIONDATA:   Input argument for FUNCTIONHANDLE.  FUNCTIONDATA must
%                      be in the form of a structure.  LINESCAN will assign
%                      values to the fields 'X', 'Y', and 'Index' prior to
%                      passing FUNCTIONDATA to FUNCTIONHANDLE.  These fields
%                      will represent the marker's x-position, y-position,
%                      and relative index, respectively, thereby giving
%                      FUNCTIONHANDLE knowledge of the marker's new
%                      position.
%
%   Notes/Limitations:
%     * If interpolation is active (INTERP=1), it is assumed that the first
%       two columns of STRINGDATA correspond to the line x- and y-data,
%       respectively.  This is required so that linear interpolation of
%       STRINGDATA behaves correctly on log scales.  (This restriction does
%       not apply to the case where FUNCTIONHANDLE and FUNCTIONDATA are used
%       in lieu of STRINGDATA).
%     * LINESCAN does not play well with auto-scaling axes!

%   Author: Adam DiVergilio, 10-99
%   Copyright 1986-2000 The MathWorks, Inc. 
%   $Revision: 1.31 $  $Date: 2000/08/08 15:02:51 $

%---If called with no input arguments, default input to callback object
if isempty(varargin)
   varargin{1} = gcbo;
end

%---If first input argument is a handle (assume line), create new marker
if ishandle(varargin{1})
   M = LocalCreateMarker(varargin{:});
   if ~isempty(get(M,'ButtonDownFcn'))
      LocalActivateMarker(M,'on');
   end
   if nargout
      varargout{1} = M;
   end
else
   %---Delete all markers
   objs = varargin{2};
   LocalDeleteMarker(findall(objs(:),'Tag','LinescanMarker'));
end


%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalCreateMarker %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
function M = LocalCreateMarker(varargin)

ni = length(varargin);

CBL = varargin{1};        % Callback Line
CBA = get(CBL,'Parent');  % Callback Axes
CBF = get(CBA,'Parent');  % Callback Figure

%---Set default properties if not provided
if ni<2, TRACKMODE = 'x';
else,    TRACKMODE = varargin{2};
end
if ni<3, INTERP = 0;
else,    INTERP = varargin{3};
end
if ni<4, STRINGSKEL = 'X: %0.3g\nY: %0.3g';
else,    STRINGSKEL = varargin{4};
end
%---Standard data used to build string
if isstr(STRINGSKEL) | (iscell(STRINGSKEL) & isstr(STRINGSKEL{1}))
   if ni<5, USERDATA = {get(CBL,'XData'),get(CBL,'YData')};
   else,    USERDATA = varargin(5:ni);
   end
   FUNCTIONHANDLE = '';
   FUNCTIONDATA = [];
   %---Merge USERDATA cells into single matrix STRINGDATA
   %---This will make sure that all USERDATA cells are column
   STRINGDATA = zeros(size(USERDATA{1}(:),1),length(USERDATA));
   for n=1:length(USERDATA)
      STRINGDATA(:,n) = USERDATA{n}(:);
   end
%---Custom function used to build string
else
   if iscell(STRINGSKEL)
      FUNCTIONHANDLE = STRINGSKEL{1};
      FUNCTIONDATA = STRINGSKEL{2};
   else
      FUNCTIONHANDLE = STRINGSKEL;
      FUNCTIONDATA = [];
   end
   STRINGSKEL = '';
   STRINGDATA = [];
end

%---Find nearest data point
[X,Y,IDX] = LocalLineProject(CBL,TRACKMODE,INTERP);

%---If no custom function has been given, perform standard build of display string
if isempty(FUNCTIONHANDLE)
   %---Interpolation on... get interpolated row from STRINGDATA
   %---(ASSUMES FIRST 2 COLUMNS OF STRINGDATA ARE LINE XDATA AND YDATA !!!)
   if INTERP
      ISLOGX = strcmpi(get(CBA,'XScale'),'log');
      ISLOGY = strcmpi(get(CBA,'YScale'),'log');
      if ISLOGX, STRINGDATA(:,1)=log2(STRINGDATA(:,1)); end
      if ISLOGY, STRINGDATA(:,2)=log2(STRINGDATA(:,2)); end
      IDATA = LocalDataInterp(STRINGDATA,IDX);
      if ISLOGX, STRINGDATA(:,1)=pow2(STRINGDATA(:,1));
                 IDATA(:,1)     =pow2(IDATA(:,1));      end
      if ISLOGY, STRINGDATA(:,2)=pow2(STRINGDATA(:,2));
                 IDATA(:,2)     =pow2(IDATA(:,2));      end
   %---Interpolation off... just get row IDX from STRINGDATA
   else
      IDATA = STRINGDATA(IDX,:);
   end
   %---Build string from STRINGSKEL and IDATA
   if iscell(STRINGSKEL)
      STRINGSKEL = strcat(STRINGSKEL,{'\n'});
      lastline = STRINGSKEL{length(STRINGSKEL)};
      STRINGSKEL{length(STRINGSKEL)} = lastline(1:length(lastline)-2);
      STRINGSKEL = cat(2,STRINGSKEL{:});
   end
   %---Pad STRINGSKEL with spaces (front and back)
   cr = sprintf('\n');
   STRINGSKEL = strrep(STRINGSKEL,'\n',cr);
   STRINGSKEL = strrep(STRINGSKEL,cr,sprintf(' %s ',cr));
   STRINGSKEL = sprintf(' %s ',STRINGSKEL);
   %---Initial display string
   STRING = feval('sprintf',STRINGSKEL,IDATA);
%---Otherwise, let custom function build display string
else
   FUNCTIONDATA.X = X;
   FUNCTIONDATA.Y = Y;
   FUNCTIONDATA.Index = IDX;
   STRING = feval(FUNCTIONHANDLE,FUNCTIONDATA);
end

%---System-dependent variables
if isunix
   MARKERSIZE = 6;
else
   MARKERSIZE = 5;
end
FONTSIZE = get(CBA,'FontSize');

%---Get axes color
AXCOLOR = get(CBA,'Color');
if isstr(AXCOLOR)
   AXCOLOR = get(CBF,'Color');
end
if sum(AXCOLOR)>1.5
  MCOLOR = [0 0 0];
  FACECOLOR = [1 1 238/255];
  EDGECOLOR = [.8 .8 .8];
else
  MCOLOR = [1 1 1];
  FACECOLOR = 48/255*[1 1 1];
  EDGECOLOR = [.5 .5 .5];
end

%---Create marker (line)
M = line('Parent',CBA,'Tag','LinescanMarker',...
   'XData',X,'YData',Y,'Color',MCOLOR,'LineStyle','none',...
   'Marker','square','MarkerSize',MARKERSIZE,'MarkerFaceColor',MCOLOR,...
   'HandleVisibility','callback','Clipping','off',...
   'ButtonDownFcn',@LocalSelectMarker);

%---Create patch
P = patch('Parent',CBA,'Tag','LinescanPatch',...
   'XData',[],'YData',[],...
   'FaceColor',FACECOLOR,'EdgeColor',EDGECOLOR,...
   'HandleVisibility','callback','Clipping','off');

%---Create text
T = text('Parent',CBA,'Tag','LinescanText',...
   'Color',MCOLOR,...
   'Position',[X Y],'String',STRING,...
   'HorizontalAlignment','left','VerticalAlignment','bottom',...
   'Interpreter','none','FontSize',FONTSIZE,...
   'HandleVisibility','callback','Clipping','off',...
   'ButtonDownFcn',@LocalSelectText);

%---Re-orient text if near top/right edges of axes
set(T,'Units','normalized');
E = get(T,'Extent');
set(T,'Units','data');
XOFF = 6;
YOFF = 6;
if (E(1)+E(3)>1) & (E(1)-E(3)>0)
   set(T,'HorizontalAlignment','right');
   XOFF = -6;
end
if (E(2)+E(4)>1) & (E(2)-E(4)>0)
   set(T,'VerticalAlignment','top');
   YOFF = -6;
end

%---Build uicontextmenu for marker text
CM0 = uicontextmenu('Parent',CBF,'Tag','LinescanUIContextMenu','HandleVisibility','callback');

%---FontSize
CM1 = uimenu(CM0,'Label',sprintf('FontSize'),'Tag','FontSize','HandleVisibility','callback');
CM2 = uimenu(CM1,'Label','8', 'Tag','8', 'Callback',{@LocalSelectMenu,'fontsize'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'FontSize',8));
CM2 = uimenu(CM1,'Label','10','Tag','10','Callback',{@LocalSelectMenu,'fontsize'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'FontSize',10));
CM2 = uimenu(CM1,'Label','12','Tag','12','Callback',{@LocalSelectMenu,'fontsize'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'FontSize',12));
CM2 = uimenu(CM1,'Label','14','Tag','14','Callback',{@LocalSelectMenu,'fontsize'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'FontSize',14));
CM2 = uimenu(CM1,'Label','16','Tag','16','Callback',{@LocalSelectMenu,'fontsize'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'FontSize',16));
CH = get(CM1,'Children');
set(findobj(CH,'flat','Tag',num2str(get(T,'FontSize'))),'Checked','on');

%---Alignment
CM1 = uimenu(CM0,'Label',sprintf('Alignment'),'Tag','Alignment','HandleVisibility','callback');
CM2 = uimenu(CM1,'Label',sprintf('Top-Right'),   'Callback',{@LocalSelectMenu,'alignment'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'H','left','V','bottom'));
CM2 = uimenu(CM1,'Label',sprintf('Top-Left'),    'Callback',{@LocalSelectMenu,'alignment'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'H','right','V','bottom'));
CM2 = uimenu(CM1,'Label',sprintf('Bottom-Right'),'Callback',{@LocalSelectMenu,'alignment'},'Sep','on',...
   'HandleVisibility','callback','UserData',struct('Marker',M,'H','left','V','top'));
CM2 = uimenu(CM1,'Label',sprintf('Bottom-Left'), 'Callback',{@LocalSelectMenu,'alignment'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'H','right','V','top'));
VA = get(T,'VerticalAlignment');
HA = get(T,'HorizontalAlignment');
CH = get(CM1,'Children');
if     strcmpi(VA,'bottom') & strcmpi(HA,'left')
   set(findobj(CH,'flat','Position',1),'Checked','on');
elseif strcmpi(VA,'bottom') & strcmpi(HA,'right')
   set(findobj(CH,'flat','Position',2),'Checked','on');
elseif strcmpi(VA,'top')    & strcmpi(HA,'left')
   set(findobj(CH,'flat','Position',3),'Checked','on');
elseif strcmpi(VA,'top')    & strcmpi(HA,'right')
   set(findobj(CH,'flat','Position',4),'Checked','on');
end

%---Interpolation
CM1 = uimenu(CM0,'Label',sprintf('Interpolation'),'Tag','Interpolation','HandleVisibility','callback');
CM2 = uimenu(CM1,'Label',sprintf('None'),'Callback',{@LocalSelectMenu,'interpolation'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'Interp',0));
CM2 = uimenu(CM1,'Label',sprintf('Linear'),'Callback',{@LocalSelectMenu,'interpolation'},...
   'HandleVisibility','callback','UserData',struct('Marker',M,'Interp',1));
CH = get(CM1,'Children');
if INTERP
   set(findobj(CH,'flat','Position',2),'Checked','on');
else
   set(findobj(CH,'flat','Position',1),'Checked','on');
end

%---Raise
CM1 = uimenu(CM0,'Label',sprintf('Raise'),'Sep','on','Callback',{@LocalSelectMenu,'raise'},...
   'HandleVisibility','callback','UserData',struct('Marker',M));

%---Delete
CM1 = uimenu(CM0,'Label',sprintf('Delete'),'Callback',{@LocalSelectMenu,'delete'},...
   'HandleVisibility','callback','UserData',struct('Marker',M));

set([M;T;P],'uicontextmenu',CM0);

%---If the line is a single point, make marker identical to line
%--- (so we don't obscure the line, but also prevent user from
%---  making further markers on the line)
%---Also, clear marker buttondownfcn since user cannot move the marker
if length(get(CBL,'XData'))==1
   set(M,...
      'ButtonDownFcn',  '',...
      'Color',          get(CBL,'Color'),...
      'LineStyle',      get(CBL,'LineStyle'),...
      'Marker',         get(CBL,'Marker'),...
      'MarkerSize',     get(CBL,'MarkerSize'),...
      'MarkerEdgeColor',get(CBL,'MarkerEdgeColor'),...
      'MarkerFaceColor',get(CBL,'MarkerFaceColor'));
end

%---Create data structure and save in UserData of marker objects
ud = struct(...
   'Line',           CBL,...
   'TrackMode',      TRACKMODE,...
   'Interp',         INTERP,...
   'StringSkel',     STRINGSKEL,...
   'StringData',     STRINGDATA,...
   'FunctionHandle', FUNCTIONHANDLE,...
   'FunctionData',   FUNCTIONDATA,...
   'Xoff',           XOFF,...
   'Yoff',           YOFF,...
   'Marker',         M,...
   'Patch',          P,...
   'Text',           T,...
   'UIContextMenu',  CM0);
set([M P T CM0],'UserData',ud);

%---Add object listeners
 LocalAddObjectListeners(M);
   
%---Add parent listeners
 LocalAddParentListeners(M);

%---Update marker (will fix text offset & patch extent)
 LocalUpdateMarker(M,'extent');


%%%%%%%%%%%%%%%%%%%%%%%%%%%
% LocalAddObjectListeners %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalAddObjectListeners(M)
 ud = get(M,'UserData');
 CBL = ud.Line;

 %---Listen for deletion of any relevant objects
 Listeners = handle.listener(CBL,'ObjectBeingDestroyed',@LocalDeleteMarker);
 Listeners.CallbackTarget = M;
 Listeners(end+1) = handle.listener(M,'ObjectBeingDestroyed',@LocalDeleteMarker);
 Listeners(end).CallbackTarget = M;
 Listeners(end+1) = handle.listener(CBL,findprop(handle(CBL),'Parent'),'PropertyPostSet',@LocalReparent);
 Listeners(end).CallbackTarget = M;

 if length(get(CBL,'XData'))==1
    %---Listeners
    cls = findclass(findpackage('hg'),'line');
    %---Listen for xdata change of line
    Listeners(end+1) = handle.listener(CBL,cls.findprop('XData'),'PropertyPostSet',{@localDataChange,M});
    %---Listen for ydata change of line
    Listeners(end+1) = handle.listener(CBL,cls.findprop('YData'),'PropertyPostSet',{@localDataChange,M});
    %---Listen for color change of line
    Listeners(end+1) = handle.listener(CBL,cls.findprop('MarkerFaceColor'),'PropertyPostSet',{@localDataChange,M});
    Listeners(end+1) = handle.listener(CBL,cls.findprop('MarkerEdgeColor'),'PropertyPostSet',{@localDataChange,M});
 end

 setappdata(M,'Listeners',Listeners);


%%%%%%%%%%%%%%%%%
% LocalReparent %
%%%%%%%%%%%%%%%%%
function LocalReparent(eventSrc,eventData)
 ud = get(eventSrc,'UserData');
 ax = eventData.NewValue;
 set([ud.Marker ud.Patch ud.Text],'Parent',ax);
 set([ud.UIContextMenu],'Parent',get(ax,'Parent'));
 LocalAddParentListeners(eventSrc);
 LocalUpdateMarker(eventSrc,'raiserefresh');


%%%%%%%%%%%%%%%%%%%%%%%%%%%
% LocalAddParentListeners %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalAddParentListeners(M)
 ud       = get(M,'UserData');
 A        = get(M,'Parent');
 L        = handle.listener(A,findprop(handle(A),'Position'),'PropertyPostSet',{@LocalRefresh,M});
 L(end+1) = handle.listener(A,findprop(handle(A),'XLim'),    'PropertyPostSet',{@LocalRefresh,M});
 L(end+1) = handle.listener(A,findprop(handle(A),'YLim'),    'PropertyPostSet',{@LocalRefresh,M});
 L(end+1) = handle.listener(A,'ObjectChildAdded',{@LocalChildAdded,M});
 L(end+1) = handle.listener(A,findprop(handle(A),'XLimMode'),'PropertyPreSet', {@LocalHide,M});
 L(end+1) = handle.listener(A,findprop(handle(A),'YLimMode'),'PropertyPreSet', {@LocalHide,M});
 setappdata(M,'ParentListeners',L);


%%%%%%%%%%%%%
% LocalHide %
%%%%%%%%%%%%%
function LocalHide(eventSrc,eventData,M)
 ud = get(M,'UserData');
 set([ud.Marker ud.Text ud.Patch],'Visible','off');


%%%%%%%%%%%%%%%%
% LocalRefresh %
%%%%%%%%%%%%%%%%
function LocalRefresh(eventSrc,eventData,M)
 LocalUpdateMarker(M,'raiserefresh');


%%%%%%%%%%%%%%%%%%%
% LocalChildAdded %
%%%%%%%%%%%%%%%%%%%
function LocalChildAdded(eventSrc,eventData,M)
 objtag  = get(eventData.Child,'Tag');
 %---Don't update markers if we are adding zoom objects (major HACK!) or other linescan markers
 if ~any(strcmp(objtag,{'_TMWZoomLines','@#$TMWzoom','LinescanMarker','LinescanText','LinescanPatch'}))
    LocalUpdateMarker(M,'raiserefresh');
 end


%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalActivateMarker %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalActivateMarker(M,action)

persistent OldLS OldLW OldLM OldPEC OldXLM OldYLM OldDB OldWBMF OldWBUF OldPTR OldPSCD OldPSHS

ud  = get(M,'UserData');
CBA = get(M,'Parent');
CBF = get(CBA,'Parent');

switch action
case 'on'
   %---Highlight line being scanned
   OldLS = get(ud.Line,'LineStyle');
   OldLW = get(ud.Line,'LineWidth');
   OldLM = get(ud.Line,'Marker');
   set(ud.Line,'LineStyle','-','LineWidth',2,'Marker','none');
   %---Highlight patch edge around scan text
   OldPEC = get(ud.Patch,'EdgeColor');
   set(ud.Patch,'EdgeColor',[0.5 0.5 0.5]);
   %---Turn off axes auto limits
   OldXLM  = get(CBA,'XLimMode');
   OldYLM  = get(CBA,'YLimMode');
   set(CBA,'XLimMode','manual','YLimMode','manual');
   %---Set mouse bindings & pointer
   OldDB   = get(CBF,'DoubleBuffer');
   OldWBMF = get(CBF,'WindowButtonMotionFcn');
   OldWBUF = get(CBF,'WindowButtonUpFcn');
   OldPTR  = get(CBF,'Pointer');
   OldPSCD = get(CBF,'PointerShapeCData');
   OldPSHS = get(CBF,'PointerShapeHotSpot');
   set(CBF,...
      'DoubleBuffer','on',...
      'WindowButtonMotionFcn',{@LocalDragMarker,M},...
      'WindowButtonUpFcn',{@LocalReleaseMarker,M});
   setptr(CBF,'closedhand');
   %---Make sure axes containing marker is on top
   axes(CBA)
   %---Then raise all legends found in figure
   LEG = findobj(get(CBF,'Children'),'flat','Type','axes','Tag','legend');
   for n=1:length(LEG)
      axes(LEG)
   end
   %---Raise and refresh selected marker
   LocalUpdateMarker(M,'raiserefresh');
   ViewerObj = get(CBF,'UserData');
   if isa(ViewerObj,'viewgui')
      status(ViewerObj,'Drag data marker to new location.');
   end
case 'off'
   %---Reset line/patch properties
   set(ud.Line,'LineStyle',OldLS,'LineWidth',OldLW,'Marker',OldLM);
   set(ud.Patch,'EdgeColor',OldPEC);
   %---Reset axes scaling
   set(CBA,'XLimMode',OldXLM,'YLimMode',OldYLM);
   %---Reset figure properties
   set(CBF,...
      'DoubleBuffer',OldDB,...
      'WindowButtonMotionFcn',OldWBMF,...
      'WindowButtonUpFcn',OldWBUF,...
      'Pointer',OldPTR,...
      'PointerShapeCData',OldPSCD,...
      'PointerShapeHotSpot',OldPSHS);
   %---Eval windowbuttonmotionfcn (to determine appropriate pointer)
   if ~isempty(OldWBMF)
      feval(OldWBMF);
   end
end


%%%%%%%%%%%%%%%%%%%%%
% LocalSelectMarker %
%%%%%%%%%%%%%%%%%%%%%
function LocalSelectMarker(eventSrc,eventData)
 downtype = get(gcbf,'SelectionType');
 switch downtype
 case 'normal'                           %---Marker Left-ButtonDown = select marker
    LocalActivateMarker(eventSrc,'on');
 case 'extend'                           %---Marker Middle-ButtonDown = rotate text
    LocalRotateMarker(eventSrc);
 case 'alt'                              %---(Marker Right-ButtonDown = uicontextmenu)
    ;
 end


%%%%%%%%%%%%%%%%%%%
% LocalDragMarker %
%%%%%%%%%%%%%%%%%%%
function LocalDragMarker(eventSrc,eventData,M)
 LocalUpdateMarker(M,'drag');


%%%%%%%%%%%%%%%%%%%%%%
% LocalReleaseMarker %
%%%%%%%%%%%%%%%%%%%%%%
function LocalReleaseMarker(eventSrc,eventData,M)
 LocalActivateMarker(M,'off');


%%%%%%%%%%%%%%%%%%%
% LocalSelectText %
%%%%%%%%%%%%%%%%%%%
function LocalSelectText(eventSrc,eventData)
 downtype = get(gcbf,'SelectionType');
 switch downtype
 case 'extend'                           %---Text Middle-ButtonDown = rotate text
    LocalRotateMarker(eventSrc);
 end


%%%%%%%%%%%%%%%%%%%
% LocalSelectMenu %
%%%%%%%%%%%%%%%%%%%
function LocalSelectMenu(eventSrc,eventData,action)
 Menu = eventSrc;
 mud = get(Menu,'UserData');
 ud  = get(mud.Marker,'UserData');
 switch action
 case 'fontsize'
    set(ud.Text,'FontSize',mud.FontSize);
 case 'alignment'
    set(ud.Text,'VerticalAlignment',mud.V,'HorizontalAlignment',mud.H);
    switch mud.V
    case 'top',    ud.Yoff = -6;
    case 'bottom', ud.Yoff =  6;
    otherwise,     ud.Yoff =  0;  end
    switch mud.H
    case 'left',   ud.Xoff =  6;
    case 'right',  ud.Xoff = -6;
    otherwise,     ud.Xoff =  0;  end
 case 'interpolation'
    ud.Interp = mud.Interp;
 case 'raise'
    LocalUpdateMarker(ud.Marker,'raiserefresh');
    return
 case 'delete'
    LocalDeleteMarker(mud.Marker);
    return
 end

 %---Set current menu selection "checked"
 set(get(get(Menu,'Parent'),'Children'),'Checked','off');
 set(Menu,'Checked','on');

 %---Update marker UserData
 set([ud.Marker;ud.Text;ud.Patch;ud.UIContextMenu],'UserData',ud);

 %---Refresh marker extent (may have changed due to property change)
 LocalUpdateMarker(ud.Marker,'extent');


%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalUpdateMarker %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalUpdateMarker(M,action)

ud = get(M,'UserData');
CBA = get(ud.Line,'Parent');
CBF = get(CBA,'Parent');
XLIM = get(CBA,'XLim');
YLIM = get(CBA,'YLim');
ISLOGX = strcmpi(get(CBA,'XScale'),'log');
ISLOGY = strcmpi(get(CBA,'YScale'),'log');
scale = 1;

%---Level of update set by 'action'
switch action
case 'drag'
   %---Find nearest data point (w.r.t. pointer)
   [X,Y,IDX] = LocalLineProject(ud.Line,ud.TrackMode,ud.Interp);
   %---If no custom function has been given, perform standard build of display string
   if isempty(ud.FunctionHandle)
      %---Interpolation on... get interpolated row from STRINGDATA
      %---(ASSUMES FIRST 2 COLUMNS OF STRINGDATA ARE LINE XDATA AND YDATA !!!)
      STRINGDATA = ud.StringData;
      if ud.Interp
         if ISLOGX, STRINGDATA(:,1)=log2(STRINGDATA(:,1)); end
         if ISLOGY, STRINGDATA(:,2)=log2(STRINGDATA(:,2)); end
         IDATA = LocalDataInterp(STRINGDATA,IDX);
         if ISLOGX, IDATA(:,1)     =pow2(IDATA(:,1));      end
         if ISLOGY, IDATA(:,2)     =pow2(IDATA(:,2));      end
      %---Interpolation off... just get row IDX from STRINGDATA
      else
         IDATA = STRINGDATA(IDX,:);
      end
      %---Build string
      STRING = feval('sprintf',ud.StringSkel,IDATA);
   %---Otherwise, let custom function build display string
   else
      ud.FunctionData.X = X;
      ud.FunctionData.Y = Y;
      ud.FunctionData.Index = IDX;
      STRING = feval(ud.FunctionHandle,ud.FunctionData);
   end
   %---Update marker position, text position, text string
   set(M,'XData',X,'YData',Y);
   set(ud.Text,'Position',[X Y],'String',STRING);
   %---Hide markers which lie outside of axes
   LocalCheckMarkerExtent(M,X,Y,XLIM,YLIM);

case 'extent'
   %---Update text position to match marker
   set(ud.Text,'Position',[get(M,'XData') get(M,'YData')]);

case 'raiserefresh'
   %---Make sure markers are siblings of their line (in case line was moved)
   TopKids = [ud.Marker;ud.Text;ud.Patch];
   set(TopKids,'Visible',get(ud.Line,'Visible'));
   %---Restack so markers are on top
   AllKids = allchild(CBA);
   i1 = find(TopKids(1)==AllKids);
   i2 = find(TopKids(2)==AllKids);
   i3 = find(TopKids(3)==AllKids);
   AllKids([i1;i2;i3])=[];
   set(CBA,'Children',[TopKids(:);AllKids(:)]);
   %---Update text position to match marker
   X = get(M,'XData');
   Y = get(M,'YData');
   set(ud.Text,'Position',[X Y]);
   %---Hide markers which lie outside of axes (visible lines only)
   if strcmpi(get(ud.Line,'Visible'),'on')
      LocalCheckMarkerExtent(M,X,Y,XLIM,YLIM);
   end
   %---For printing, scale the offset
   op = get(CBF,'OuterPosition');
   fp = get(CBF,'Position');
   if op(4)<fp(4) & strcmpi(get(CBA,'Units'),'points')
      pp = get(get(CBA,'Parent'),'PaperPosition');
      scale = fp(4)/pp(4);
   end

end

%---Set text offset (in pixels)
set(ud.Text,'Units','pixel');
PP = get(ud.Text,'Position');
set(ud.Text,'Position',[PP(1)+ud.Xoff*scale PP(2)+ud.Yoff*scale]);

%---Set new patch coordinates
set(ud.Text,'Units','Normalized');
E = get(ud.Text,'Extent');
if ISLOGX
   XL = log2(XLIM(2)/XLIM(1));
   X1 = pow2(XL*E(1)+log2(XLIM(1)));
   X2 = pow2(XL*E(3)+log2(X1));
else
   XL = XLIM(2) - XLIM(1);
   X1 = XLIM(1) + XL*E(1);
   X2 = X1 + XL*E(3);
end
if ISLOGY
   YL = log2(YLIM(2)/YLIM(1));
   Y1 = pow2(YL*E(2)+log2(YLIM(1)));
   Y2 = pow2(YL*E(4)+log2(Y1));
else
   YL = YLIM(2) - YLIM(1);
   Y1 = YLIM(1) + YL*E(2);
   Y2 = Y1 + YL*E(4);
end
set(ud.Patch,...
   'XData',[X1,X2,X2,X1,X1],...
   'YData',[Y1,Y1,Y2,Y2,Y1]);

%---Reset text units to data
set(ud.Text,'Units','data');


%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalRotateMarker %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalRotateMarker(M)
 ud   = get(M,'UserData');
 ch   = get(findobj(ud.UIContextMenu,'Tag','Alignment'),'Children');
 mold = findobj(ch,'flat','Checked','on');
 pold = get(mold,'Position');
 pdif = [2 -1 1 -2];
 pnew = pold+pdif(pold);
 mnew = findobj(ch,'flat','Position',pnew);
 LocalSelectMenu(mnew,[],'alignment');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalCheckMarkerExtent %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalCheckMarkerExtent(M,xp,yp,xlims,ylims);
 ud = get(M,'UserData');
 if xp>max(xlims) | xp<min(xlims) | yp>max(ylims) | yp<min(ylims)
    set([ud.Marker;ud.Patch;ud.Text],'Visible','off');
 else
    set([ud.Marker;ud.Patch;ud.Text],'Visible','on');
 end


%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalDeleteMarker %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalDeleteMarker(eventSrc,eventData)
 if isempty(eventSrc), return; end
 fig = get(get(eventSrc(1),'Parent'),'Parent');
 for n=1:length(eventSrc)
    %---Delete all objects associated with marker
    if ishandle(eventSrc(n))
       ud = get(eventSrc(n),'UserData');
       DeleteMe = [ud.Marker ud.Patch ud.Text ud.UIContextMenu];
       idx = find(ishandle(DeleteMe));
       set(DeleteMe(idx),'DeleteFcn',' ');
       delete(DeleteMe(idx));
    end
 end
 ViewerObj = get(fig,'UserData');
 if isa(ViewerObj,'viewgui')
    if n==1
       status(ViewerObj,'Data marker deleted.',1);
    else
       status(ViewerObj,sprintf('%d data markers deleted.',n),1);
    end
 end


%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalLineProject %%%
%%%%%%%%%%%%%%%%%%%%%%%%
function [xp,yp,ip] = LocalLineProject(LINE,TRACKMODE,INTERP)

%---Line XY-Data
xline = get(LINE,'XData');
yline = get(LINE,'YData');

%---If line is single point, just return the point
if length(xline)==1
   xp = xline;
   yp = yline;
   ip = 1;
   return;
end

%---Axes CurrentPoint
AXES = get(LINE,'Parent');
CP = get(AXES,'CurrentPoint');
x = CP(1,1);
y = CP(1,2);

%---Transform log-scale data for linear scanning
ISLOGX = strcmpi(get(AXES,'XScale'),'log');
ISLOGY = strcmpi(get(AXES,'YScale'),'log');
if ISLOGX
   x     = log2(x);
   xline = log2(xline);
end
if ISLOGY
   y     = log2(y);
   yline = log2(yline);
end

switch TRACKMODE
%---Perform 'XY' tracking (good for data which crosses back on itself in x)
case 'xy'
   [xp,yp,ip] = lproject(x,y,xline,yline,AXES);

%---Perform 'X' tracking (good for standard data which is monotonic in x)
otherwise
   xp = min(max(x,min(xline)),max(xline));
   i1 = sum(xline<=xp);
   if i1==length(xline)
      ip = i1;
      yp = yline(ip);
   else
      i2 = (xp-xline(i1))/(xline(i1+1)-xline(i1));
      ip = i1+i2;
      yp = yline(i1)*(1-i2) + yline(i1+1)*(i2);
   end
end

if ~INTERP
   ip = round(ip);
   xp = xline(ip);
   yp = yline(ip);
end

if ISLOGX, xp = pow2(xp); end
if ISLOGY, yp = pow2(yp); end


%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalDataInterp %%%
%%%%%%%%%%%%%%%%%%%%%%%
function IDATA = LocalDataInterp(DATA,IDX)
 %---Assumes IDX lies within valid range of DATA
 i1 = floor(IDX);
 i2 = IDX-i1;
 %---Prevents indexing error for case i1=size(DATA,1);
 if i2
    IDATA = DATA(i1,:)*(1-i2)+DATA(i1+1,:)*(i2);
 else
    IDATA = DATA(i1,:);
 end


%%%%%%%%%%%%%%%%%%%
% localDataChange %
%%%%%%%%%%%%%%%%%%%
function localDataChange(eventSrc,eventData,M)
 %---Listener callback for line data change
 set(M,eventSrc.Name,eventData.NewValue);
 LocalUpdateMarker(M,'extent');
