1 """
2 Basic interactor for Reflectometry profile.
3 """
4
5 from reflutils import active_color
6 from matplotlib import transforms
7 from matplotlib.axes import Subplot
8 from binder import pixel_to_data
9 if hasattr(transforms,'blended_transform_factory'):
10
11 blend_xy = transforms.blended_transform_factory
12 else:
13
14 blend_xy = transforms.blend_xy_sep_transform
15
16
17
18
19
20
22 """
23 Abstract base class for someone who use interactor
24
25 Share some functions between the interface interactor and various layer
26 interactors.
27
28 Individual interactors need the following functions:
29
30 save(ev) - save the current state for later restore
31 restore() - restore the old state
32 move(x,y,ev) - move the interactor to position x,y
33 moveend(ev) - end the drag event
34 update() - draw the interactors
35
36 The following are provided by the base class:
37
38 connect_markers(markers) - register callbacks for all markers
39 clear_markers() - remove all items in self.markers
40 onHilite(ev) - enter/leave event processing
41 onLeave(ev) - enter/leave event processing
42 onClick(ev) - mouse click: calls save()
43 onRelease(ev) - mouse click ends: calls moveend()
44 onDrag(ev) - mouse move: calls move() or restore()
45 onKey(ev) - keyboard move: calls move() or restore()
46
47 Interactor attributes:
48
49 base - model we are operating on
50 axes - axes holding the interactor
51 color - color of the interactor in non-active state
52 markers - list of handles for the interactor
53 """
54 - def __init__(self,
55 base,
56 axes,
57 color='black'
58 ):
59 self.base = base
60 self.axes = axes
61 self.xcoords = blend_xy(axes.transData, axes.transAxes)
62 self.color = color
63 self.infopanel = base.parent.infopanel
64 self.model = base.model
65 self._save_n = 0
66 self._save_depth_n = 0
67 self.click_flag = 0
68
69
71 """Clear old markers and interfaces."""
72 for h in self.markers:
73 h.remove()
74 if self.markers:
75 self.base.connect.clear(*self.markers)
76 self.markers = []
77
78
79
82
85
86 - def move(self, x, y, ev):
88
91
94
97
100
101
102
104 n = self.model.find( x )
105 if abs(self.model.offset[n+1]-x)<3:
106 ret = n
107 else:
108 ret = n-1
109 if ret < 0 : return 0
110 else: return ret
111
112
114 """
115 Connect markers to callbacks
116 """
117 for h in markers:
118 connect = self.base.connect
119 connect('enter', h, self.onHilite)
120 connect('leave', h, self.onLeave)
121 connect('click', h, self.onClick)
122 connect('release', h, self.onRelease)
123 connect('drag', h, self.onDrag)
124 connect('key', h, self.onKey)
125
126
128 """
129 Hilite the artist reporting the event, indicating that it is
130 ready to receive a click.
131 """
132 event.artist.set_color(active_color)
133 self.base.draw()
134 self.click_flag = 0
135
136 return True
137
138
140 """
141 Restore the artist to the original colour when the cursor leaves.
142 """
143 event.artist.set_color(self.color)
144 self.base.draw()
145 return True
146
147
149 """
150 Prepare to move the artist. Calls save() to preserve the state for
151 later restore().
152 """
153
154 self.click_flag = 1
155
156
157
158
159 transform = event.artist.get_transform()
160 xy = pixel_to_data(transform, event.x, event.y)
161 event.xdata, event.ydata = xy
162
163
164 self.clickx = event.xdata
165 self.clicky = event.ydata
166 self.save(event)
167
168
169 self._save_n = self.base.model.find( event.xdata )
170
171
172 self._save_depth_n = self.BestDepthLayerNum(event.xdata)
173
174
175 self.showValue(event)
176
177
178 return True
179
180
181
183 """
184 Release the mouse
185 """
186 self.moveend(event)
187
188
189 self.click_flag = 0
190
191 return True
192
193
195 """
196 Move the artist. Calls move() to update the state, or restore() if
197 the mouse leaves the window.
198 """
199
200 inside,prop = self.axes.contains(event)
201
202 if inside:
203 self.clickx = event.xdata
204 self.clicky = event.ydata
205
206
207 if self.click_flag ==0:
208 self._save_depth_n = self.BestDepthLayerNum(event.xdata)
209
210
211 self.click_flag = 1
212
213 self.move(event.xdata, event.ydata, event)
214
215
216 self.setValue(event)
217
218 else:
219 self.restore()
220
221
222 self.base.update()
223
224 return True
225
226
227
229 """
230 Respond to keyboard events. Arrow keys move the widget. Escape
231 restores it to the position before the last click.
232
233 Calls move() to update the state. Calls restore() on escape.
234 """
235 if event.key == 'escape':
236
237 self.restore()
238
239 elif event.key in ['up', 'down', 'right', 'left']:
240
241
242 if hasattr(self, 'clickx') and hasattr(self, 'clicky'):
243
244 dx,dy=self.dpixel(self.clickx,self.clicky,nudge=event.control)
245
246 if event.key == 'up': self.clicky += dy
247 elif event.key == 'down': self.clicky -= dy
248 elif event.key == 'right': self.clickx += dx
249 else: self.clickx -= dx
250
251
252
253 self.move(self.clickx, self.clicky, event)
254
255
256
257 event.xdata = self.clickx
258 event.ydata = self.clicky
259
260 self.setValue(event)
261
262 else:
263 return False
264
265 self.base.update()
266
267 return True
268
269
270
271 - def dpixel(self,x,y,nudge=False):
272 """
273 Return the step size in data coordinates for a small
274 step in screen coordinates.
275 If nudge is False (default) the step size is one pixel.
276 If nudge is True, the step size is 0.2 pixels.
277 """
278 ax = self.axes
279 if isinstance(ax, Subplot):
280 step = 0.01
281 else:
282 step = 0.5
283
284 px,py = ax.transData.inverted().transform( (x,y) )
285 if nudge:
286 nx,ny = ax.transData.xy_tup( (px+0.2, py+0.02) )
287 else:
288 nx,ny = ax.transData.xy_tup( (px+1, py+step) )
289 dx = nx-x
290 dy = ny-y
291
292 return dx,dy
293