function polyH = drawpolygon(action,varargin)
% Draw a closed polygon on an axes using the mouse.
%
% SYNTAX:  h = drawpolygon(axeshandle,options)
%
% The user clicks to indicate the vertices.  The segment currently being
% drawn is animated. The process is ended by clicking on the starting point
% or by double-clicking the last vertex.  A right click cancels and the
% empty matrix is returned.
%
% h is a handle to the line object generated.  If there are no arguments,
% the current axes are used.  Options are parameter, value pairs.
% Parameters can be 'LineWidth', 'Color', 'LineStyle', and 'EndThreshold'.
% Case is ignored.
%
% In order to make clicking the starting point easier, the segment being
% drawn snaps to the beginning point if the cursor comes within EndThreshold
% pixels.  Default is 5.
%
% This can be used as or within a callback function for an axes object.  If
% gcbo is not empty, the current position of the pointer is used as the
% starting vertex.  Otherwise drawpolygon waits for the next mouse click.
%
% EXAMPLES:
%
% polyH = drawpolygon(gca,'Color','r','LineWidth',3)
%
% set(gca,'ButtonDownFcn','drawpolygon(gcbo,''LineStyle'',''--'')')
%
% NOTE:
%
% Error checking is inadequate.  Probably ought to pass line options
% directly to the line command, which would enable abbreviations to be used.
% The animation is slow when the plot is complicated; I haven't tried much
% optimization.  This is 2-D only.  If there are no other children of the
% axes and autoscaling is allowed, things get a bit messy.  (Perhaps I
% ought to turn scaling to manual.)
%
% AUTHOR:
% Steven Howell, University of Hawaii
% showell@soest.hawaii.edu
%


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Main routine is mostly a switchyard
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargin<1				% Assume current axes if no
  action = gca;				% axes given.
end

if ischar(action)			% This is a callback
  try
    feval(action, varargin{:});      	% Switchyard
  catch
    disp(lasterr);
  end
else					% Initial call.  Start routine
  %Simple error check.
  if ~(ishandle(action) & strcmp('axes',get(action,'Type')))
    error('drawpolygon.m requires an axes handle')
  end
  dp_init(action,varargin{:});
  aud = get(action,'UserData');		% Save axes UserData.
  set(action,'UserData','pending');
  waitfor(action,'UserData');		% Block execution until polygon
  if nargout > 0			% is done.
    polyH = get(action,'UserData');	% Polygon handle is passed back
  end					% via axes UserData.
  set(action,'UserData',aud);		% Restore UserData.
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% SUBROUTINES
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function dp_init(axH,varargin)
figH = get(axH,'Parent');
set(figH,'DoubleBuffer','on', 'Renderer','painters');

% Set up defaults
dp.linewidth = 1;
dp.color = 'k';
dp.linestyle = '-';
dp.threshold = 5;

%Parse input arguments
for ii=1:2:length(varargin)
  switch(lower(varargin{ii}))
   case {'linewidth'}
    dp.linewidth = varargin{ii+1};
   case {'color'}
    dp.color = varargin{ii+1};
   case {'linestyle'}
    dp.linestyle = varargin{ii+1};
   case {'endthreshold'}
    dp.threshold = varargin{ii+1};
  end
end
% The variable dp stores data required for drawing the polygon
setappdata(figH,'drawpolydata',dp);
% dpa contains data required to restore figure and axes properties once
% drawpolygon is done.
dpa.axesbuttondownfcn = get(axH,'ButtonDownFcn');
setappdata(figH,'DPAuxdata',dpa);

% If this is called as a callback function, then drawing should begin
% immediately. Oterwise set the axes so the next click will start the
% drawing process.
if isempty(gcbo)
  set(axH,'ButtonDownFcn',[mfilename '(''dp_start'',gcbo)']);
else
  dp_start(axH);
end

%---------------------------------------------------------------------

function dp_start(axH)			% First button press
figH = get(axH,'Parent');
dp = getappdata(figH,'drawpolydata');
dpa = getappdata(figH,'DPAuxdata');

set(axH,'ButtonDownFcn','');
dpa.windowbuttondownfcn = get(figH,'WindowButtonDownFcn');
dpa.windowbuttonmotionfcn = get(figH,'WindowButtonMotionFcn');
dpa.windowbuttonupfcn = get(figH,'WindowButtonUpFcn');
dpa.pointer = get(figH,'Pointer');
setappdata(figH,'DPAuxdata',dpa);

dp.drawaxes = axH;
curptfig = get(figH,'CurrentPoint');
curptax = get(axH,'CurrentPoint');
curx = curptax(1,1);
cury = curptax(1,2);
dp.startptfig = curptfig;		% Save starting point in fig coords
dp.segx = [curx curx];			% seg is the animated line used to
dp.segy = [cury cury];			% set the next polygon segment.
dp.poly.x = curx;			% poly is the growing polygon
dp.poly.y = cury;
dp.polyH = line(dp.poly.x, dp.poly.y, ...
		'LineWidth',dp.linewidth, ...
		'Color', dp.color', ...
		'LineStyle', dp.linestyle);
dp.segH = line(dp.segx, dp.segy, ...
		'LineWidth',dp.linewidth, ...
		'Color', dp.color', ...
		'LineStyle', dp.linestyle);
setappdata(figH,'drawpolydata',dp);

set(figH,'WindowButtonDownFcn', [mfilename '(''dp_winbuttondown'',gcbf)'], ...
	 'WindowButtonMotionFcn', [mfilename '(''dp_movemouse'',gcbf)'], ...
	 'WindowButtonUpFcn','', ...
	 'Pointer','custom', ...
	 'PointerShapeCData',xcursor, ...
	 'PointerShapeHotSpot',xcursor('hotspot'));

%---------------------------------------------------------------------

function dp_movemouse(figH)		% Mouse moved--change the segment
dp = getappdata(figH,'drawpolydata');	% being displayed.
axH = dp.drawaxes;
curptfig = get(figH,'CurrentPoint');	% Get the current point
if all(abs(curptfig-dp.startptfig)<=dp.threshold) % Snap to start?
  dp.segx(2) = dp.poly.x(1);		% if so, set end of segment at
  dp.segy(2) = dp.poly.y(1);		% starting point.
else					% otherwise use current point
  curptax = get(axH,'CurrentPoint');
  dp.segx(2) = curptax(1,1);
  dp.segy(2) = curptax(1,2);
end
set(dp.segH, 'XData', dp.segx, 'YData', dp.segy); % Change segment data
drawnow					% force display to change

%---------------------------------------------------------------------

function dp_winbuttondown(figH)		% Button pressed
dp = getappdata(figH,'drawpolydata');
axH = dp.drawaxes;
curptfig = get(figH,'CurrentPoint');	% Get the current point

if strcmp('alt',get(figH,'SelectionType')) % Right click.  Cancel!
  delete(dp.segH);
  delete(dp.polyH);
  dpa = getappdata(figH,'DPAuxdata');
  set(figH,'WindowButtonDownFcn',dpa.windowbuttondownfcn, ...
	   'WindowButtonMotionFcn',dpa.windowbuttonmotionfcn, ...
	   'WindowButtonUpFcn',dpa.windowbuttonupfcn, ...
	   'Pointer', dpa.pointer);
  set(axH,'ButtonDownFcn',dpa.axesbuttondownfcn);
  rmappdata(figH,'DPAuxdata');
  rmappdata(figH,'drawpolydata');
  set(axH,'UserData',[]);		% Signal main routine that
                                        % drawing is done.
  return
end

if strcmp('open',get(figH,'SelectionType')) % Double click.  End!
  curptfig = dp.startptfig;		% Fake current point to look like
end					% end of polygon. (Note that the
                                        % first of the two clicks set the
                                        % final vertex.)

% At start?  If not, add new point to polygon & reset segment origin
if ~all(abs(curptfig-dp.startptfig)<=dp.threshold)
  curptax = get(axH,'CurrentPoint');
  curx = curptax(1,1);
  cury = curptax(1,2);
  dp.segx = [curx curx];
  dp.segy = [cury cury];
  dp.poly.x(end+1) = curx;
  dp.poly.y(end+1) = cury;
  set(dp.polyH, 'XData', dp.poly.x, 'YData', dp.poly.y);
  set(dp.segH, 'XData', dp.segx, 'YData', dp.segy); % Change segment data
  drawnow
  setappdata(figH,'drawpolydata',dp);
else % Otherwise set the polygon and clean up
  dp.poly.x(end+1) = dp.poly.x(1);
  dp.poly.y(end+1) = dp.poly.y(1);
  set(dp.polyH, 'XData', dp.poly.x, 'YData', dp.poly.y);
  delete(dp.segH);			% No longer neede
  drawnow;
  dpa = getappdata(figH,'DPAuxdata');
  set(figH,'WindowButtonDownFcn',dpa.windowbuttondownfcn, ...
	   'WindowButtonMotionFcn',dpa.windowbuttonmotionfcn, ...
	   'WindowButtonUpFcn',dpa.windowbuttonupfcn, ...
	   'Pointer', dpa.pointer);
  set(axH,'ButtonDownFcn',dpa.axesbuttondownfcn);
  rmappdata(figH,'DPAuxdata');
  rmappdata(figH,'drawpolydata');
  set(axH,'UserData',dp.polyH);		% Signal main routine that the
                                        % polygon is complete.
end

%---------------------------------------------------------------------

function [x,y] = figpos2data(figH,axH,figpt);
% Given a mouse position [x y] in figure coordinates, calculate position
% in axes data units. The new x and y are returned separately because
% they will be put into XData and YData separately.
% For the moment, assume that figure position is in pixels and axes
% position is normalized.  I'm not positive this routine will work
% otherwise. 
% This would be easy to vectorize, but it will never be called with more
% than 1 point and speed is critical.

axespt = get(axH,'CurrentPoint');
x = axespt(1,1);
y = axespt(1,2);
% $$$ figpos = get(figH,'position');
% $$$ fracfigpt = figpt./figpos(3:4);
% $$$ axespos = get(axH,'position');
% $$$ fracaxespt = (fracfigpt - axespos(1:2))./axespos(3:4);
% $$$ xlim = get(axH,'XLim');
% $$$ ylim = get(axH,'YLim');
% $$$ 
% $$$ if(strcmp('log',get(axH,'XScale')))
% $$$   xlog = 1;
% $$$   xlim = log10(xlim);
% $$$ else
% $$$   xlog=0;
% $$$ end
% $$$ if(strcmp('log',get(axH,'YScale')))
% $$$   ylog = 1;
% $$$   ylim = log10(ylim);
% $$$ else
% $$$   ylog=0;
% $$$ end
% $$$ 
% $$$ x = xlim(1) + fracaxespt(1).*diff(xlim);
% $$$ y = ylim(1) + fracaxespt(2).*diff(ylim);
% $$$ 
% $$$ if xlog; x = 10.^x; end
% $$$ if ylog; y = 10.^y; end

