MATLAB: How to get the right axes current point in a GUI after clicking on a pushbutton

axescurrentpointMATLABpushbutton

Here's the code for a small GUI that encapsulates my problem. After pushing the button, the user is expected to click somewhere on the image. If the point chosen lies inside the limits of the image, a point is plotted there. If it lies outside, nothing happens.
function testGUI
f = figure('Visible', 'off', 'Position', [360, 500, 450, 285]);
hbutton = uicontrol('Style', 'pushbutton', 'String', 'Add Point', 'Position', [315, 220, 90, 25]);
ha = axes('Units', 'pixels', 'Position', [50, 60, 200, 185]);
imagesc(ha, [1,2; 3,4])
colormap gray
f.Visible = 'on';
hbutton.Callback = @(h, e)buttonCallback(h, e, ha);
function buttonCallback(h, e, ha)
h.String = 'Click on image...';
waitforbuttonpress
pointerPosition = getPointerPosition;
if ~any(pointerPosition < 0.5) && ~any(pointerPosition > 2.5)
hold on
plot(ha, pointerPosition(1), pointerPosition(2), 'ro')
hold off
end
h.String = 'Add Point';
function pointerPosition = getPointerPosition
currentPoint = get(gca, 'CurrentPoint');
pointerPosition = currentPoint(1, 1:2);
To make my GUI more robust, I want to make sure that if the user accidentally clicks on any button while the code is stopped at waitforbuttonpress, the currently running instance of buttonCallback returns without doing anything. This would happen if the cursor position provided by the axes property CurrentPoint corresponded to the actual position of the pointer while clicking on the button, which lies outside of the axes.
Sadly, the property CurrentPoint of the axes only seems to correspond to the last point clicked inside the GUI but outside any button. That means that if, for instance, I click on some point inside the axes when the button isn't expecting a user input then I click two times on the button, a point will be added at the last point I clicked inside the axes before clicking the button, which is unacceptable.
Is there a way to get the axes property CurrentPoint to give the actual position of the pointer relative to the axes after I click on a button? If not, is there some other non-clunky way I can make buttonCallback know not to plot a point that wasn't provided by the user in the way intended, i.e. after clicking on the button? I know I could disable the button while it is expecting a user input, but the actual GUI I'm using is big and many things would have to be disabled. If there is a shorter way to achieve this, I'd rather know.
Thank you.

Best Answer

Since you don't already use the axes callback, I have modified your example so it will ignore all clicks outside the axis (and old clicks).
There are two things to be aware of. Firstly, this will not block execution of other (button) callbacks, and secondly, you will have to set the callback for any object you create inside the axis, as show in the creator function below.
Note that higher level functions like imshow will wipe a lot of callback properties.
function testGUI
f = figure('Visible', 'off', 'Position', [360, 500, 450, 285]);
hbutton = uicontrol('Style', 'pushbutton', 'String', 'Add Point', 'Position', [315, 220, 90, 25]);
ha = axes('Units', 'pixels', 'Position', [50, 60, 200, 185]);
imagesc(ha, [1,2; 3,4])
colormap gray
f.Visible = 'on';
hbutton.Callback = @buttonCallback;
ha.ButtonDownFcn=@AxesCallback;
children=ha.Children;
for n=1:numel(children)
children(n).ButtonDownFcn=@AxesCallback;
end
handles=struct;handles.f=f;handles.ha=ha;handles.hbutton=hbutton;
handles.waitforbuttonpress=false;%set default
guidata(f,handles)
end
function buttonCallback(h, e)
handles=guidata(h);
handles.hbutton.String = 'Click on image...';
handles.waitforbuttonpress=true;
guidata(handles.f,handles)
end
function pointerPosition = getPointerPosition
currentPoint = get(gca, 'CurrentPoint');
pointerPosition = currentPoint(1, 1:2);
end
function AxesCallback(h,e)
handles=guidata(h);
if ~handles.waitforbuttonpress
return
end
pointerPosition = getPointerPosition;
if ~any(pointerPosition < 0.5) && ~any(pointerPosition > 2.5)
hold on
plot(handles.ha, pointerPosition(1), pointerPosition(2), 'ro')
hold off
end
handles.hbutton.String = 'Add Point';
handles.waitforbuttonpress=false;
guidata(handles.f,handles)
end