function varargout = draglines(varargin)
%
% draglines.m--Permits mouse-dragging of lines in a plot.
%
% Line-dragging can be freeform or restricted to either the x or
% y-direction by specifying the 'action' argument to be 'x', 'y' or 'xy'.
% The default in the absence of an action string is 'xy'.
%
% An action of 'revert' returns the line(s) to their original location. An
% action of 'off' freezes the line in its current position, which then
% becomes its "revert" position.
%
% A second string argument containing a Matlab command can be included in
% calls to enable line-dragging. This string will be evaluated and run
% after each line-drag is complete.
%
% The current offset of line(s) from their original position can be queried
% by calling draglines.m with a single output argument and either zero
% input arguments or a single input argument specifying which lines to
% query.
%
% If a line is not specified, all lines in the current axes are affected.
%
% Syntax: <offset> = draglines(<lineHndl>,<action>)
%
% e.g., x=linspace(0,40,200);h(1)=line(x,sin(x));h(2)=line(x,cos(x./2),'color','r');
% set(gca,'xlim',[-10 50],'ylim',[-1.5 1.5]); set(gcf,'pos',[1039 530 240 195]);
% draglines
% draglines(h)
% draglines('x')
% draglines(h,'y')
% draglines(h,'xy',['sprintf(''[%d %d %d]'',get(gco,''color''))'])
% draglines(h,'revert')
% draglines('revert')
% draglines(h,'off')
% draglines('off')
% offset = draglines
% offset = draglines(h)
% Developed in Matlab 6.5.1.199709 (R13) Service Pack 1 on SUN OS 5.8.
% Kevin Bartlett(kpb@hawaii.edu), 2004/04/05, 11:04
%------------------------------------------------------------------------------
% Define constants.
XDRAG = 1;
YDRAG = 2;
XYDRAG = 3;
REVERT = 4;
OFF = 5;
STARTDRAG = 6;
ENDDRAG = 7;
REMOVEZOOMSTATE = 8;
QUERY = 9;
% Test to see if this is a recursive call to draglines.m. Recursive calls
% have the format: draglines(lineHndl,'recursion','action'), where 'action'
% is a string specifying the action to be taken.
if nargin == 3 & isstr(varargin{2}) & strcmp(varargin{2},'recursion')==1
lineHndl = varargin{1};
isRecursive = 1;
actionStr = varargin{3};
else
isRecursive = 0;
end % if
% If not a recursive call, still need to sort out the input arguments.
if isRecursive == 0
if nargin>3
error([mfilename '.m--Incorrect number of input arguments.']);
end % if
actionStr = 'xy'; % Default
lineHndl = 'lineHndl not specified'; % Dummy value
postDragActionStr = '';
for argCount = 1:nargin
thisArg = varargin{argCount};
if all(ishandle(thisArg))
lineHndl = thisArg;
else
if ~isstr(thisArg)
error([mfilename '.m--actionStr and postDragActionStr input arguments must be strings.']);
else
if ismember(thisArg,{'x' 'y' 'xy' 'revert' 'off'})
actionStr = thisArg;
else
postDragActionStr = thisArg;
end % if
end % if
end % if
end % for
% If lineHndl was not specified by user, it will currently have a
% string value. In this case, lineHndl should contain handles to all
% lines in the current axes.
if isstr(lineHndl)
lineHndl = findobj(gca,'type','line');
end % if
% If draglines.m was called with an output argument, it is being called
% to query the status of any lines. In this case, override the
% actionStr and postDragActionStr values.
if nargout > 0
if isempty(lineHndl)
varargout{1} = [];
else
actionStr = 'query';
postDragActionStr = '';
end % if
end % if
%disp(['actionStr is ' actionStr]);
%disp(['postDragActionStr is ' postDragActionStr]);
%disp(['lineHndl is ' lineHndl]);
end % if not recursive
if ~all(ishandle(lineHndl)==1) | ~all(ismember(get(lineHndl,'type'),'line'))
error([mfilename '.m--Input argument ''lineHndl'' must be a vector of handles to line objects.']);
end % if
numLines = length(lineHndl);
actionStr = lower(actionStr);
if strcmp(actionStr,'x')
action = XDRAG;
elseif strcmp(actionStr,'y')
action = YDRAG;
elseif strcmp(actionStr,'xy')
action = XYDRAG;
elseif strcmp(actionStr,'revert')
action = REVERT;
elseif strcmp(actionStr,'off')
action = OFF;
elseif strcmp(actionStr,'startdrag')
action = STARTDRAG;
elseif strcmp(actionStr,'enddrag')
action = ENDDRAG;
elseif strcmp(actionStr,'removezoomstate')
action = REMOVEZOOMSTATE;
elseif strcmp(actionStr,'query')
action = QUERY;
else
error([mfilename '.m--Unrecognised action string: ''' actionStr '''.']);
end % if
%disp(['Action is: ' actionStr])
%disp(['Handles are ' sprintf('%f\n',lineHndl)])
if isRecursive == 0 & ismember(action,[XDRAG YDRAG XYDRAG]);
% Remove any zoom state.
draglines(lineHndl,'recursion','removezoomstate');
set(gcf,'pointer','fleur');
for lineCount = 1:numLines
thisLine = lineHndl(lineCount);
set(thisLine,'ButtonDownFcn',['draglines( gco, ''recursion'', ''startdrag'' )']);
setappdata(thisLine,'draglines_action',action);
setappdata(thisLine,'draglines_offset',[0 0]);
setappdata(thisLine,'postDragActionStr',postDragActionStr);
end % for
elseif isRecursive == 0 & action == REVERT
for lineCount = 1:numLines
thisLine = lineHndl(lineCount);
setappdata(thisLine,'draglines_action',action);
xdata = get(thisLine,'xdata');
ydata = get(thisLine,'ydata');
offset = getappdata(thisLine,'draglines_offset');
set(thisLine,'xdata',xdata-offset(1),'ydata',ydata-offset(2));
setappdata(thisLine,'draglines_offset',[0 0]);
setappdata(thisLine,'postDragActionStr',postDragActionStr);
end % for
elseif isRecursive == 0 & action == OFF
set(lineHndl,'ButtonDownFcn',[]);
set(gcf,'pointer',get(0,'factoryFigurePointer'));
for lineCount = 1:numLines
thisLine = lineHndl(lineCount);
setappdata(thisLine,'draglines_offset',[0 0]);
end % for
elseif isRecursive == 1 & action == STARTDRAG
% Remove any zoom state.
draglines(lineHndl,'recursion','removezoomstate');
thisLine = gco;
currentPoint = get(gca,'CurrentPoint');
currentPoint = [currentPoint(1,1) currentPoint(1,2)];
setappdata(thisLine,'draglines_lastPos',currentPoint);
set(gcf,'WindowButtonUpFcn',['draglines( gco, ''recursion'', ''enddrag'' )']);
elseif isRecursive == 1 & action == ENDDRAG
thisLine = gco;
endPoint = get(gca,'currentpoint');
endPoint = [endPoint(1,1) endPoint(1,2)];
lastPos = getappdata(thisLine,'draglines_lastPos');
moveBy = endPoint - lastPos;
% Find out if dragging in x-direction, y-direction, or mixed x-y
% direction.
thisAction = getappdata(thisLine,'draglines_action');
if thisAction == XDRAG
moveBy(2) = 0;
elseif thisAction == YDRAG
moveBy(1) = 0;
end % if
xdata = get(thisLine,'xdata');
ydata = get(thisLine,'ydata');
set(thisLine,'xdata',xdata+moveBy(1),'ydata',ydata+moveBy(2));
oldOffset = getappdata(thisLine,'draglines_offset');
newOffset = oldOffset + moveBy;
setappdata(thisLine,'draglines_offset',newOffset);
set(gcf,'WindowButtonUpFcn','');
% Evaluate post-dragging action string.
postDragActionStr = getappdata(thisLine,'postDragActionStr');
eval(postDragActionStr);
elseif isRecursive == 1 & action == REMOVEZOOMSTATE
% Switch to non-zoom state.
set(gcf,'WindowButtonDownFcn','');
% Get handles to axes' zoom buttons.
HiddenState = get(0,'ShowHiddenHandles');
set(0,'ShowHiddenHandles','on');
figToolZoomIn = findobj('tag','figToolZoomIn');
figToolZoomOut = findobj('tag','figToolZoomOut');
set(0,'ShowHiddenHandles',HiddenState);
set(figToolZoomIn,'state','off');
set(figToolZoomOut,'state','off');
end % if
% Return an output argument, if requested.
if nargout == 1
if action == QUERY
offsetsOut = zeros(numLines,2);
for lineCount = 1:numLines
thisLine = lineHndl(lineCount);
thisLineOffsets = getappdata(thisLine,'draglines_offset');
if ~isempty(thisLineOffsets)
offsetsOut(lineCount,:) = thisLineOffsets;
end % if
end % for
varargout{1} = offsetsOut;
else
% Return empty variable if output requested for non-query call.
varargout{1} = [];
end % if
elseif nargout ~= 0
error([mfilename '.m--Wrong number of output arguments.']);
end % if