-
Notifications
You must be signed in to change notification settings - Fork 8
/
fn_buttonmotion.m
234 lines (200 loc) · 6.89 KB
/
fn_buttonmotion.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
function varargout = fn_buttonmotion(fun,varargin)
%FN_BUTTONMOTION Execute a task while mouse pointer is moved (try 'fn_buttonmotion demo')
%---
% function varargout = fn_buttonmotion(fun[,hf][,'doup'][,'pointer',pointer])
% function [moved ...] = fn_buttonmotion(...,'moved?')
% function fn_buttonmotion('demo')
%---
% utility for executing a task while mouse button is pressed
%
% See also fn_moveobject
% Thomas Deneux
% Copyright 2007-2017
% Note on the implementation: unfortunately, it is not possible to simply
% use the figure 'BusyAction' and 'Interruptible' properties. Indeed, they
% act on all callbacks, whereas we would like to treat differently the
% motion callbacks (cancel if system is busy) and the button press
% callbacks (queue if system is busy).
if nargin==0, help fn_buttonmotion, return, end
disp_if_debug('entering fn_buttonmotion function')
% Input
if ischar(fun) && strcmp(fun,'demo')
demo()
return
end
hf = []; doup = false; pointer = ''; checkmove = false;
k = 0;
while k<length(varargin)
k = k+1;
a = varargin{k};
if ischar(a)
switch a
case 'doup'
doup = true;
case 'pointer'
k = k+1;
pointer = varargin{k};
case 'moved?'
checkmove = true;
otherwise
error 'unknown flag'
end
else
hf = a;
end
end
if isempty(hf), hf = gcf; end
% How many outputs of the function are expected
nout = max(0,nargout-checkmove);
% fn_buttonmotion did not terminate correctly last time?
motionfcn = get(hf,'WindowButtonMotionFcn');
if iscell(motionfcn) && isequal(motionfcn{1},@motionexec)
% check whether there was a queued stop command
stopdebugstr = getappdata(hf,'fn_buttonmotion_queuestop');
% terminate previous call in any case
disp_if_debug('New call to fn_buttonmotion in this figure before the previous one was finished: terminate the previous one first')
terminate(hf)
disp_if_debug('Now go on with the new call to fn_buttonmotion')
else
stopdebugstr = '';
end
% Backup properties that will be overriden
state = fn_get(hf,{'WindowButtonDownFcn' 'WindowButtonUpFcn' 'WindowButtonMotionFcn' 'Interruptible' 'Pointer'});
setappdata(hf,'fn_buttonmotion_savestate',state)
% Motion
set(hf,'WindowButtonMotionFcn',{@motionexec 'move' fun nout}, ...
'WindowButtonUpFcn',{@motionexec 'stop'},'WindowButtonDownFcn',{@motionexec 'stop2'})
set(hf,'Interruptible','on') % make sure events will be interruptible, to not miss the 'stop' event
if ~isempty(pointer), set(hf,'pointer',pointer), end
setappdata(hf,'fn_buttonmotion_scrolling',true)
setappdata(hf,'fn_buttonmotion_busy',false)
setappdata(hf,'fn_buttonmotion_moved',false)
setappdata(hf,'fn_buttonmotion_lastmoverejected',false)
if nout, setappdata(hf,'fn_buttonmotion_output',cell(1,nout)), end
% Wait for motion end
if ~isempty(stopdebugstr)
% if there was a queued stop command do not start
disp_if_debug(['The previous call had queued command ' stopdebugstr ', so stop new call immediately'])
else
disp_if_debug('waiting for motion end')
waitfor(hf,'WindowButtonMotionFcn') % at motion end, function terminate will be executed, and this property will be changed
disp_if_debug('finished waiting')
end
% Finish
if ~ishandle(hf), return, end % figure has been closed in the mean while
if doup || any(getappdata(hf,'fn_buttonmotion_lastmoverejected'))
setappdata(hf,'fn_buttonmotion_scrolling',false)
exec(fun,nout,hf);
try rmappdata(hf,'fn_buttonmotion_scrolling'), end %#ok<TRYNC>
end
try rmappdata(hf,'fn_buttonmotion_lastmoverejected'), end %#ok<TRYNC>
if checkmove
varargout = {getappdata(hf,'fn_buttonmotion_moved')};
else
varargout = {};
end
try rmappdata(hf,'fn_buttonmotion_moved'), end %#ok<TRYNC>
if nout
varargout = [varargout getappdata(hf,'fn_buttonmotion_output')]; %#ok<VARARG>
try rmappdata(hf,'fn_buttonmotion_output'), end %#ok<TRYNC>
end
%---
function motionexec(hf,~,actionflag,fun,nout)
persistent kid
if isempty(kid), kid = 0; end
kid = fn_mod(kid+1,1000);
% start execution
debugstr = [actionflag ' ' num2str(kid)];
disp_if_debug(['start ' debugstr])
if strcmp(actionflag,'stop2'), actionflag = 'stop'; end
% custom queuing/canceling system
if getappdata(hf,'fn_buttonmotion_busy')
switch actionflag
case 'move' % cancel
setappdata(hf,'fn_buttonmotion_lastmoverejected',true)
disp_if_debug(['rejct ' debugstr])
return
case 'stop' % queue
setappdata(hf,'fn_buttonmotion_queuestop',debugstr)
disp_if_debug(['queue ' debugstr])
return
end
elseif strcmp(actionflag,'move')
setappdata(hf,'fn_buttonmotion_lastmoverejected',false)
end
setappdata(hf,'fn_buttonmotion_busy',true)
% stop (not queued)
if strcmp(actionflag,'stop')
disp_if_debug(['end ' debugstr])
terminate(hf)
return
end
% evaluate function
disp_if_debug(['exec ' debugstr])
try
exec(fun,nout,hf);
catch ME
terminate(hf)
rethrow(ME)
end
% end of current execution
disp_if_debug(['end ' debugstr])
drawnow % allow queued events to be processed (and canceled, because of 'fn_buttonmotion_busy' flag)
setappdata(hf,'fn_buttonmotion_moved',true)
setappdata(hf,'fn_buttonmotion_busy',false)
% stop (queued)
try
debugstr = getappdata(hf,'fn_buttonmotion_queuestop');
if ~isempty(debugstr)
disp_if_debug(['exec queued ' debugstr])
terminate(hf)
end
end
%---
function exec(fun,nout,hf)
if nargin>=2 && nout, out = cell(1,nout); end
if ischar(fun)
evalin('base',fun);
elseif isa(fun,'function_handle')
if nout, [out{:}] = feval(fun); else feval(fun); end
elseif iscell(fun)
if nout, [out{:}] = feval(fun{:}); else feval(fun{:}); end
else
error bad
end
if nout, setappdata(hf,'fn_buttonmotion_output',out), end
%---
function terminate(hf)
state = getappdata(hf,'fn_buttonmotion_savestate');
fn_set(hf,state)
% remove data attached to the figure, except 'moved', 'lastmoverejected'
% and 'output' which will be needed in the main fn_buttonmotion after
% terminate(hf) has executed
rmappdata(hf,'fn_buttonmotion_savestate')
rmappdata(hf,'fn_buttonmotion_busy')
if isappdata(hf,'fn_buttonmotion_queuestop'), rmappdata(hf,'fn_buttonmotion_queuestop'), end
rmappdata(hf,'fn_buttonmotion_scrolling')
%---
function disp_if_debug(varargin)
if eval('false')
str = [];
for k=1:nargin
x = varargin{k};
if iscell(x)
if isa(x{1},'function_handle')
x = func2str(x{1});
else
error('don''t know how to display cell array')
end
end
str = [str x]; %#ok<AGROW>
end
disp(str)
end
%---
function demo
C = {'figure(1), clf'
'ht = uicontrol(''style'',''text'',''backgroundcolor'',''y'');'
'fun = @()set(ht,''string'',num2str(get(1,''CurrentPoint'')));'
'set(1,''buttondownfcn'',@(u,e)fn_buttonmotion(fun,''doup''))'};
fn_dispandexec(C)