1 '''
2 Provides basic magnification services.
3
4 @author: Eitan Isaacson
5 @author: Peter Parente
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2006 IBM Corp
8
9 @license: I{The BSD License}
10 All rights reserved. This program and the accompanying materials are made
11 available under the terms of the BSD license which accompanies
12 this distribution, and is available at
13 U{http://www.opensource.org/licenses/bsd-license.php}
14 '''
15
16 from AccessEngine import AEScript, AccessEngineAPI
17 from AccessEngine import AEConstants
18 from Tools.i18n import bind, _
19 from AccessEngine.AEConstants import CMD_GOTO, CMD_GET_ROI
20 from math import cos, sin, atan, pi, sqrt
21
22
23 __uie__ = dict(kind='script', tier=None, all_tiers=True)
24
26 '''
27 Class that deals with seamless panning and repositioning of the magnifier.
28
29 @ivar smooth_pan: Allow smooth panning
30 @type smooth_pan: boolean
31 @ivar accel: Acceleration in px/sec**2
32 @type accel: integer
33 @ivar velocity: Velocity in px/sec
34 @type velocity: integer
35 @cvar pan_rate: Duration in milliseconds between "frames"
36 @type pan_rate: integer
37 @cvar _end_coord: Coordinate that we are trying to get to
38 @type _end_coord: tuple
39 @cvar _curr_velocity: Current velocity
40 @type _curr_velocity: integer
41 @cvar _curr_coord: Current coordinates
42 @type _curr_coord: tuple
43 @cvar _vect_length: vector length
44 @type _vect_length: number
45 '''
46 pan_rate = 40
47 smooth_pan = None
48 accel = None
49 velocity = None
50 _curr_velocity = None
51 _curr_coord = None
52 _vect_length = None
53 _end_coord = None
54
55 - def goTo(self, layer, end_coord, start_coord=None, smooth=False):
56 '''
57 Main public method for putting the magnifier on a coordinate.
58
59 @param layer: Layer
60 @type layer: integer
61 @param end_coord: Coordinate we want to reach.
62 @type end_coord: tuple
63 @param start_coord: Coordinate we are starting at, if none is given,
64 there will be no panning.
65 @type start_coord: tuple
66 @param smooth: Use smooth panning?
67 @type smooth: boolean
68 '''
69 if start_coord and (self.smooth_pan or smooth):
70 self._panTo(end_coord, start_coord)
71 else:
72 self._hopTo(end_coord, layer)
73
74 - def _hopTo(self, end_coord, layer):
75 '''
76 Stop panning and hop to given coordinate.
77
78 @param end_coord: Coordinate we want to reach.
79 @type end_coord: tuple
80 @param layer: Layer
81 @type layer: integer
82 '''
83 self.stopPan()
84 self.goToPos(end_coord, layer)
85
86 - def _panTo(self, end_coord, start_coord):
87 '''
88 Pan to given coordinate.
89
90 @param end_coord: Coordinate we want to reach.
91 @type end_coord: tuple
92 @param start_coord: Coordinate we are starting at.
93 @type start_coord: tuple
94 '''
95 if end_coord == self._end_coord:
96 return
97
98 self.stopPan()
99
100 self._curr_coord = float(start_coord[0]), float(start_coord[1])
101
102 self._end_coord = end_coord
103
104 x = self._end_coord[0] - self._curr_coord[0]
105 y = self._end_coord[1] - self._curr_coord[1]
106
107 self._vect_length = sqrt(x**2 + y**2)
108
109 if (x, y) == (0, 0):
110 return
111 elif y == 0 and x > 0:
112 angle = pi/2
113 elif y == 0 and x < 0:
114 angle = -pi/2
115 elif y < 0:
116 angle = atan (float(x)/float(y)) + pi
117 else:
118 angle = atan (float(x)/float(y))
119
120 self._curr_velocity = 0
121
122 self._x_step = sin(angle)
123 self._y_step = cos(angle)
124
125 self.startPan()
126
168
170 '''
171 Stop current pan motion.
172 '''
173 raise NotImplementedError
174
176 '''
177 Start a timeout and execute _panstep
178 '''
179 raise NotImplementedError
180
181
183 '''
184 Actual method that translates the zoomer, to be implemented by script.
185
186 @param pos: Position
187 @type pos: tuple
188 @param layer: Layer
189 @type layer: integer
190 '''
191 raise NotImplementedError
192
194 '''
195 Defines a single set of state variables for all instances of the
196 L{BasicMagScript} and their tasks. The variables are configuration settings
197 that should be respected in all instances of L{BasicMagScript}.
198
199 - B{MouseTrack (bool):} Track cursor
200 - B{FocusTrack (bool):} Track focus
201 - B{CaretTrack (bool):} Track caret
202 - B{SmoothPan (bool):} Smooth pan
203 - B{CaretPan (bool):} Smooth caret panning
204 - B{PanAccel (int):} Pan acceleration
205 - B{PanVelocity (int):} Pan velocity
206 '''
208 '''
209 Create L{AEState} settings for this L{BasicMagScript}.
210 '''
211 self.newBool('MouseTrack', True, _('Track cursor'),
212 _('Magnifier is to track mouse movemenself.'))
213 self.newBool('FocusTrack', True, _('Track focus'),
214 _('Magnifier is to track focused area'))
215 self.newBool('CaretTrack', True, _('Track caret'),
216 _('Magnifier is to track text caret'))
217 self.newBool('SmoothPan', False, _('Smooth panning'),
218 _('Magnifier will pan to next selection'))
219 self.newBool('CaretPan', False, _('Smooth caret panning'),
220 _('Magnifier will smoothly pan on caret movements, by '
221 'default it only pans on new lines.'))
222 self.newRange('PanAccel', 40, _('Pan Acceleration'), 1, 100, 0,
223 _('Acceleration rate of magnifier pan motion (pixels per second '
224 'squared).'))
225 self.newRange('PanVelocity', 600, _('Pan Velocity'), 1, 1000, 0,
226 _('Velocity of magnifier pan motion (pixels per second).'))
227 self.newBool('MagReview', False, _('Track review keys?'),
228 _('When set, reviewing is tracked by the magnifier. '
229 'Otherwise, magnifier is not updated when reviewing.'))
230
232 '''
233 Gets configurable settings for this L{AEScript <AEScript.AEScript>}.
234
235 @return: Group of all configurable settings
236 @rtype: L{AEState.Setting.Group}
237 '''
238 g = self.newGroup()
239 t = g.newGroup(_('Tracking'))
240 t.extend(['MouseTrack',
241 'FocusTrack',
242 'CaretTrack'])
243 p = g.newGroup(_('Kinematics'))
244 p.extend(['SmoothPan',
245 'CaretPan',
246 'PanAccel',
247 'PanVelocity'])
248 r = g.newGroup(_('Reviewing'))
249 r.append('MagReview')
250 return g
251
253 '''
254 A script to provide magnification for a user.
255 It defines special hotkeys.
256 - I{(Alt+Caps-Lock+PAGE_UP)} - increase the zoom rate
257 - I{(Alt+Caps-Lock+PAGE_DOWN)} - decrease the zoom rate
258
259 @cvar STATE: L{BasicMagScriptState} object that holds user setting values.
260 @type STATE: L{AEState}
261 @cvar FUDGE: Fudge the Calculation of mouse pointer position
262 @type FUDGE: integer
263 @ivar mousepos: Coordinates of the mouse pointer position
264 @type mousepos: tuple
265 @ivar parent_por_focus: The parent accessible of a focused element
266 @type parent_por_focus: L{AEPor}
267 @ivar parent_por_select: The parent accessible of a selected element
268 @type parent_por_select: L{AEPor}
269 '''
270 STATE=BasicMagScriptState
271 FUDGE=5
272
274 '''
275 Registers L{event tasks <AEScript.event_tasks>} to handle
276 L{focus <AEEvent.FocusChange>}, L{caret <AEEvent.CaretChange>},
277 L{selector <AEEvent.SelectorChange>}, and L{mouse <AEEvent.MouseChange>}
278 change events. Registers L{tasks <AEScript.registered_tasks>} that can be
279 mapped to L{AEInput.Gesture}s.
280 '''
281 self.mousepos = (0, 0)
282 self.parent_por_focus = None
283 self.parent_por_select = None
284
285 AccessEngineAPI.setScriptIdealOutput(self, 'magnifier')
286
287
288 self.registerEventTask('mag focus', AEConstants.EVENT_TYPE_FOCUS_CHANGE)
289 self.registerEventTask('mag caret', AEConstants.EVENT_TYPE_CARET_CHANGE)
290 self.registerEventTask('mag mouse', AEConstants.EVENT_TYPE_MOUSE_CHANGE)
291 self.registerEventTask('mag selector',
292 AEConstants.EVENT_TYPE_SELECTOR_CHANGE)
293
294
295 self.registerTask('mag review', self.onReviewChange)
296 self.registerTask('mag increase zoom', self.changeZoomLevel)
297 self.registerTask('mag decrease zoom', self.changeZoomLevel)
298
299
300 kbd = AccessEngineAPI.getInputDevice(None, 'keyboard')
301 AccessEngineAPI.addInputModifiers(self, kbd, kbd.AEK_CAPS_LOCK)
302
303
304 pairs = [[kbd.AEK_ALT_L, kbd.AEK_CAPS_LOCK],
305 [kbd.AEK_ALT_R, kbd.AEK_CAPS_LOCK]]
306
307 for pair in pairs:
308
309 self.registerCommand(kbd, 'mag increase zoom', _('mag increase zoom'),
310 False, pair+[kbd.AEK_PAGE_UP])
311 self.registerCommand(kbd, 'mag decrease zoom', _('mag decrease zoom'),
312 False, pair+[kbd.AEK_PAGE_DOWN])
313
314
315 self.accel = self.state.PanAccel*(self.pan_rate/1000.0)
316 self.velocity = self.state.PanVelocity*(self.pan_rate/1000.0)
317 self.smooth_pan = self.state.SmoothPan
318
319 for setting in self.state:
320 setting.addObserver(self._updateSetting)
321
322
323 self.last_caret_offset = 0
324
325
326 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
327 'review previous item', 'ReviewScript')
328 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
329 'review current item', 'ReviewScript')
330 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
331 'review next item', 'ReviewScript')
332 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
333 'review previous word', 'ReviewScript')
334 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
335 'review current word', 'ReviewScript')
336 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
337 'review next word', 'ReviewScript')
338 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
339 'review previous char', 'ReviewScript')
340 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
341 'review current char', 'ReviewScript')
342 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
343 'review next char', 'ReviewScript')
344 self.chainTask('mag review', AEConstants.CHAIN_AFTER,
345 'pointer to por', 'ReviewScript')
346
348 '''
349 Update special given settings.
350
351 @param state:
352 @type state:
353 @param setting: Name of attribute
354 @type setting: string
355 '''
356 name = setting.name
357 value = setting.value
358 if name == 'PanAccel':
359 self.accel = value*(self.pan_rate/1000.0)
360 elif name == 'PanVelocity':
361 self.velocity = value*(self.pan_rate/1000.0)
362 elif name == 'SmoothPan':
363 self.smooth_pan = value
364
366 '''
367 Provides the localized name of this L{AEScript <AEScript.AEScript>}.
368
369 @return: Human readable name of this script.
370 @rtype: string
371 '''
372 return _('Basic magnifier')
373
375 '''
376 Describe what this special L{AEScript} do.
377
378 @return: Human readable translated description of this script.
379 @rtype: string
380 '''
381 return _('Manages basic screen magnification services. Requires a '
382 'loaded magnifier device.')
383
385 '''
386 Send command to magnifier device to center zoomer on given coordinate.
387
388 @param pos: x,y coordinate
389 @type pos: tuple of integer
390 @param layer: Layer
391 @type layer: integer
392 '''
393 roi = AccessEngineAPI.getMagROI(self, layer, cap='magnifier', role='output')
394 if roi is None:
395 return
396 x1, y1, x2, y2 = roi
397 w, h = x2 - x1, y2 - y1
398 x1 = pos[0] - w/2
399 y1 = pos[1] - h/2
400 x2, y2 = x1 + w, y1 + h
401 AccessEngineAPI.setMagGoto(self, layer, (x1, y1, x2, y2),
402 cap='magnifier', role='output')
403
405 '''
406 Get ROI from magnifier and return center coordinate.
407
408 @param layer: Layer
409 @type layer: integer
410 @return: Magnifier's focal point.
411 @rtype: tuple
412 '''
413 roi = AccessEngineAPI.getMagROI(self, layer, cap='magnifier', role='output')
414 if roi is None:
415 return (0, 0, 0, 0)
416 else:
417 return roi
418
420 '''
421 Calculates the desired focal point for a specific POR.
422 And retuns current focal point, and desired focal point.
423
424 @param por: POR (Point of Regard) that's focal point needs to be determined.
425 @type por: L{AEPor}
426 @param track_mouse: Take the current position of the mouse pointer into
427 account so that the magnifier doesn't jerk?
428 @type track_mouse: boolean
429 @param kwargs: Arbitrary keyword arguments to pass to the task
430 @type kwargs: dictionary
431 '''
432
433 try:
434 por_width, por_height = AccessEngineAPI.getAccVisualExtents(por)
435 except TypeError:
436
437
438
439 track_mouse = False
440
441 pos_x, pos_y = AccessEngineAPI.getAccPosition(por)
442
443 m_x, m_y = self.mousepos
444
445 end_x, end_y = AccessEngineAPI.getAccVisualPoint(por)
446
447
448
449 roi_x1, roi_y1, roi_x2, roi_y2 = self.getPos(kwargs['layer'])
450
451 start_pos = ((roi_x1 + roi_x2)/2, (roi_y1 + roi_y2)/2)
452
453 if (track_mouse and
454 (m_x >= pos_x-self.FUDGE) and (m_x <= (pos_x+por_width+self.FUDGE)) and
455 (m_y >= pos_y-self.FUDGE) and (m_y <= (pos_y+por_height+self.FUDGE))):
456
457
458
459
460 end_x = m_x
461 end_y = m_y
462
463 return start_pos, (end_x, end_y)
464
465
466
467
469 '''
470 Moves the magnifier to coordinate of this focused element.
471
472 @param kwargs: Arbitrary keyword arguments to pass to the task
473 @type kwargs: dictionary
474 '''
475 if not self.state.FocusTrack:
476 return True
477
478 if kwargs['por'] != self.parent_por_focus:
479 start_pos, end_pos = self.getDesiredFocalPoint(**kwargs)
480 self.goTo(kwargs['layer'], end_pos, start_pos)
481
482
483 self.parent_por_focus = AccessEngineAPI.getParentAcc(kwargs['por'])
484
485
486
487
489 '''
490 Moves the magnifier to coordinate of this caret.
491
492 @param kwargs: Arbitrary keyword arguments to pass to the task.
493 @type kwargs: dictionary
494 '''
495 if not self.state.CaretTrack:
496 return True
497
498 por = kwargs['por']
499
500
501 text_len = AccessEngineAPI.getAccTextLength(por)
502 if (por.char_offset < 0 or
503 por.item_offset < 0 or
504 text_len < (por.char_offset + por.item_offset)):
505 return
506
507 start_pos, end_pos = self.getDesiredFocalPoint(**kwargs)
508
509
510 if (por.item_offset != self.last_caret_offset or
511 self.state.CaretPan):
512 self.goTo(kwargs['layer'], end_pos, start_pos)
513 else:
514 self.goTo(kwargs['layer'], end_pos)
515
516 self.last_caret_offset = por.item_offset
517
518
519
520
522 '''
523 Moves the magnifier to coordinate of this selected element.
524
525 @param kwargs: Arbitrary keyword arguments to pass to the task.
526 @type kwargs: dictionary
527 '''
528 if not self.state.FocusTrack:
529 return True
530
531 if kwargs['por'] != self.parent_por_select:
532 start_pos, end_pos = self.getDesiredFocalPoint(**kwargs)
533 self.goTo(kwargs['layer'], end_pos, start_pos)
534
535 self.parent_por_select = AccessEngineAPI.getParentAcc(kwargs['por'])
536
537
538
539
541 '''
542 Execute on each mouse event, moves the magnifier to coordinate of mouse.
543
544 @param kwargs: Arbitrary keyword arguments to pass to the task.
545 @type kwargs: dictionary
546 @return: C{True} to allow other tasks to process this event.
547 @rtype: boolean
548 '''
549 if not self.state.MouseTrack:
550 return True
551 self.mousepos = kwargs['pos']
552 self.goTo(kwargs['layer'], kwargs['pos'])
553
554
555
556
558 '''
559 Synchronizes the magnified area to the text at the pointer when reviewing.
560
561 @param kwargs: Arbitrary keyword arguments to pass to the task.
562 @type kwargs: dictionary
563 '''
564 if self.getScriptSettingVal('MagReview'):
565 result = self.getTempData('review')
566 if result in (AEConstants.REVIEW_OK, AEConstants.REVIEW_WRAP):
567 start_pos, end_pos = self.getDesiredFocalPoint(track_mouse=False,
568 **kwargs)
569 self.goTo(kwargs['layer'], end_pos, start_pos)
570
572 '''
573 Increases or decreases zoom level.
574
575 @param kwargs: Arbitrary keyword arguments to pass to the task.
576 @type kwargs: dictionary
577 '''
578 print AccessEngineAPI.getMagROI(self, kwargs['layer'],
579 cap='magnifier', role='output')
580 s = AccessEngineAPI.getStyleSetting(self, 'Zoom', **kwargs)
581 id = kwargs['task_name']
582 if id.find('increase') > -1:
583 s.value += 10**(-s.precision)
584 else:
585 s.value -= 10**(-s.precision)
586 print AccessEngineAPI.getMagROI(self, kwargs['layer'],
587 cap='magnifier', role='output')
588
589
590
591
597
599 '''
600 Unregister pan animation timer task.
601 '''
602 try:
603 self.unregisterTimerTask('mag pan step')
604 except KeyError:
605 pass
606
608 '''
609 Execute on timed intervals. Used to animate pan magnifier movement.
610
611 @param kwargs: Arbitrary keyword arguments to pass to the task.
612 @type kwargs: dictionary
613 '''
614 rv = self._panStep(kwargs['layer'])
615 if not rv:
616 self.unregisterTimerTask(kwargs['task_name'])
617