1
2 '''
3 A customize script for the Evolution E-Mail client.
4
5 @author: Ramona Bunk
6 @organization: IT Science Center Ruegen gGmbH, Germany
7 @copyright: Copyright (c) 2007, 2008 ITSC Ruegen
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 which accompanies this distribution, and
12 is available at U{http://www.opensource.org/licenses/bsd-license.php}
13 '''
14
15 from AccessEngine import AEScript, AccessEngineAPI
16 from AccessEngine import AEConstants
17 from AccessEngine.AEPor import AEPor
18 from Tools.i18n import bind, _
19
20 import logging
21 log = logging.getLogger('EvolutionScript')
22
23
24 __uie__ = dict(kind='script', tier='evolution', all_tiers=False)
25
27 '''
28 Configuration of SUE for the Evolution application.
29 '''
31 self.newBool('ReadMailHeader',
32 True, _('Read the mail header when the mail is opened?'),
33 _('When checked, SUE will automaticly read the header of the '
34 'e-mail, when opened in a new window.'))
35 self.newBool('ReadMailBody',
36 True, _('Read the mail text when the mail is opened?'),
37 _('When checked, SUE will automaticly read the text of the '
38 'e-mail, when opened in a new window.'))
39
41 groupReadMail = self.newGroup()
42 groupReadMail.append('ReadMailHeader')
43 groupReadMail.append('ReadMailBody')
44 return groupReadMail
45
47 '''
48 The customized script for the Evolution E-Mail client.
49
50 @ivar saRootPOR: the first common Ancestor for all sites in the evolution
51 setup assistant (accessible path: 0,0)
52 @type saRootPOR: L{AEPor}
53 @ivar saActiveChild: the active panel in the evolution setup assistant; a
54 direct child of saRootPOR
55 @type saActiveChild: L{AEPor}
56 @ivar htmlPanel: the HTML Container of the open mail
57 @type htmlPanel: L{AEPor}
58 @ivar lastQuotationLevel: the depth of quotation in an e-mail
59 @type lastQuotationLevel: integer
60 '''
61 STATE = EvolutionScriptState
63 '''Initialization of the EvolutionScript.'''
64
65 self.saRootPOR = None
66 self.saActiveChild = None
67 self.htmlPanel = None
68 self.lastTreeTableItem = None
69 self.lastQuotationLevel = 0
70 self.lastPOR = None
71
72
73 AccessEngineAPI.setScriptIdealOutput(self, 'audio')
74
75
76
77 self.chainNewEventTaskAround('evolution view change',
78 'read view', 'BasicSpeechScript',
79 'onViewGained', 'onViewLost')
80 self.chainNewEventTaskAround('evolution focus change',
81 'read focus', 'BasicSpeechScript',
82 'onFocusGained')
83 self.chainNewEventTaskAround('evolution selector change',
84 'read selector', 'BasicSpeechScript',
85 'onSelectorActive')
86 self.chainNewEventTaskAround('evolution caret change',
87 'read caret', 'BasicSpeechScript',
88 'onCaretMoved')
89
90
91 self.registerEventTask('lost view', AEConstants.EVENT_TYPE_VIEW_CHANGE,
92 tier = True)
93 self.registerEventTask('read mail folder tree',
94 AEConstants.EVENT_TYPE_SELECTOR_CHANGE, tier = True)
95 self.registerEventTask('sa read next panel', \
96 AEConstants.EVENT_TYPE_STATE_CHANGE, tier = True)
97
98
99 self.registerTask('read email', self.readEMail)
100 self.chainTask('read email', AEConstants.CHAIN_AROUND,
101 'read item text', 'BasicSpeechScript')
102
103 self.registerTask('read label', self.onViewGained)
104 self.registerTask('number of mails', self.getNumberOfMails)
105
106
107 kbd = AccessEngineAPI.getInputDevice(None, 'keyboard')
108 AccessEngineAPI.addInputModifiers(self, kbd, kbd.AEK_ALT_L,
109 kbd.AEK_SHIFT_L, kbd.AEK_ALT_R,
110 kbd.AEK_SHIFT_R, kbd.AEK_CAPS_LOCK)
111 pairs = [[kbd.AEK_ALT_L, kbd.AEK_SHIFT_L], [kbd.AEK_ALT_R, kbd.AEK_SHIFT_R]]
112
113
114 for pair in pairs:
115 self.registerCommand(kbd, 'read label',
116 _('read the label in the Setup Assistant'),
117 False, pair+[kbd.AEK_R])
118
119
120
121
122
123
124
125
126
128 '''
129 Reads the name of the current tree table if it has changed.
130 '''
131 text = AccessEngineAPI.getAccName(kwargs['por'])
132 if (self.lastPOR is None):
133 AccessEngineAPI.saySection(self, cap='audio', role='output', text=text,
134 **kwargs)
135 elif (not AccessEngineAPI.hasAccRole('tree table', self.lastPOR)):
136 AccessEngineAPI.saySection(self, cap='audio', role='output', text=text,
137 **kwargs)
138 else:
139 textLP = AccessEngineAPI.getAccName(self.lastPOR)
140 if (text != textLP):
141 AccessEngineAPI.saySection(self, cap='audio', role='output', text=text,
142 **kwargs)
143
144 self.lastPOR = kwargs['por']
145 AccessEngineAPI.inhibitMayStop()
146
175
177 '''
178 Reset the evolution states.
179 '''
180 self.saRootPOR = None
181 self.saActiveChild = None
182 self.htmlPanel = None
183 self.lastTreeTableItem = None
184 self.lastQuotationLevel = 0
185
186 return self._doReplacedTask(**kwargs)
187
189 '''
190 Replaces the L{BasicSpeechScript} when Evolution gets a L{FocusChange} Event.
191
192 @return: C{True}; more tasks can execute for this event.
193 @rtype: boolean
194 '''
195
196 rv = True
197
198
199 if self._isSetupAssistant(kwargs['por']):
200 self.lastPOR = None
201
202 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs)
203
204
205
206
207 if not self.__workaroundSALabeledBy(**kwargs):
208 self.doTask('read new label', 'BasicSpeechScript', **kwargs)
209
210 self.doTask('read new role', 'BasicSpeechScript', **kwargs)
211
212 AccessEngineAPI.inhibitMayStop()
213
214
215 elif (AccessEngineAPI.hasAccRole('table cell', kwargs['por'])):
216 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs)
217 parentPOR = AccessEngineAPI.getParentAcc(kwargs['por'])
218 if (AccessEngineAPI.hasAccRole('tree table', parentPOR)):
219 por = kwargs['por']
220 kwargs['por'] = parentPOR
221 self._readNewTreeTableName(**kwargs)
222 kwargs['por'] = por
223 self._readTableRowFromMailList(**kwargs)
224
225
226 elif (self._isMailWindow(**kwargs)):
227 if (AccessEngineAPI.hasAccRole('scroll pane', kwargs['por'])):
228 pass
229 elif (AccessEngineAPI.hasAccRole('unknown', kwargs['por'])):
230 if self.state.ReadMailHeader:
231 self.readMailHeader(**kwargs)
232 if self.state.ReadMailBody:
233 self.readMailBody(**kwargs)
234
235
236 else:
237 if (AccessEngineAPI.hasAccRole('tree table', kwargs['por'])):
238 self._readNewTreeTableName(**kwargs)
239 else:
240 self.lastPOR = None
241 rv = self._doReplacedTask(**kwargs)
242 return rv
243
245 '''
246 Replaces the L{BasicSpeechScript} when Evolution gets a L{SelectorChange}
247 Event.
248
249 @return: C{True}; more tasks can execute for this event.
250 @rtype: boolean
251 '''
252 rv = True
253
254
255 if kwargs['task_name'] == 'evolution selector change':
256
257
258 if (AccessEngineAPI.hasAccRole('table cell', kwargs['por'])):
259 pass
260
261
262 elif (self._isMailWindow(**kwargs)):
263 if (AccessEngineAPI.hasAccRole('scroll pane', kwargs['por']) or
264 (AccessEngineAPI.hasAccRole('unknown', kwargs['por']))):
265 pass
266 else:
267 rv = self._doReplacedTask(**kwargs)
268
269
270 elif (AccessEngineAPI.hasAccRole('text', kwargs['por'])):
271 kwargs['text'] = AccessEngineAPI.getAccText(kwargs['por'])
272 self.readEMail(**kwargs)
273
274 else:
275
276
277 if (AccessEngineAPI.hasAccRole('tree table', kwargs['por'])):
278 if self.lastTreeTableItem == kwargs['text']:
279 return False
280 self.lastTreeTableItem = kwargs ['text']
281 rv = self._doReplacedTask(**kwargs)
282
283
284 elif kwargs['task_name'] == 'read mail folder tree':
285
286
287
288 if (AccessEngineAPI.hasAccRole('tree table', kwargs['por'])):
289 if self.lastTreeTableItem == kwargs['text']:
290 return False
291 self.lastTreeTableItem = kwargs ['text']
292
293 self.unchainTask('evolution selector change', AEConstants.CHAIN_AROUND,
294 'read selector', 'BasicSpeechScript')
295 rv = self.doTask('read selector', 'BasicSpeechScript', **kwargs)
296 self.chainTask('evolution selector change', AEConstants.CHAIN_AROUND,
297 'read selector', 'BasicSpeechScript')
298
299 return rv
300
302 '''
303 React to a L{StateChange} event in Evolution.
304
305 We are interested in state changes in the Evolution Setup Assistant, where
306 the state will be set to 'showing'. This should indicate that a new panel in
307 the dialog was activated.
308
309 @return: C{True}; more tasks can execute for this event.
310 @rtype: boolean
311 '''
312
313
314 if (kwargs['name'] == 'showing' and kwargs['value'] == 1 \
315 and self.saRootPOR == AccessEngineAPI.getParentAcc(kwargs['por'])):
316
317
318
319
320
321 oldActivePanel = self.saActiveChild
322 self._findSAActivePanel()
323 if (self.saActiveChild == kwargs['por'] and \
324 self.saActiveChild is not oldActivePanel):
325 self._readSALabels(**kwargs)
326
327 return True
328
330 '''
331 Don't execute the caret move, when in the list with the e-mails.
332 '''
333 if (AccessEngineAPI.hasAccRole('table cell', kwargs['por'])):
334 return False
335 else:
336 return self._doReplacedTask(**kwargs)
337
338
339
340
342 '''
343 Test whether the given por is an element in the Evolution Setup Assistant
344
345 @param por: The L{AEPor} that should be in the Evolution Setup Assistant
346 @type por: L{AEPor}
347
348 @return: whether the por is in the Evolution Setup Assitant
349 @rtype: boolean
350 '''
351 if not por: return False
352
353 rootPor = AccessEngineAPI.getRootAcc(por)
354 if self.saRootPOR:
355 return (self.saRootPOR == AccessEngineAPI.getChildAcc(0, rootPor))
356 else:
357 rootPor = AccessEngineAPI.getChildAcc(0, por)
358 if not rootPor: return False
359
360
361
362
363 childCount = AccessEngineAPI.getAccCount(rootPor)
364
365 if (childCount < 10): return False
366
367
368
369 else:
370 lastChild = AccessEngineAPI.getChildAcc(childCount-1, rootPor)
371 lastChildCount = AccessEngineAPI.getAccCount(lastChild)
372 if (AccessEngineAPI.hasAccRole('filler', lastChild) and \
373 lastChildCount == 5):
374 for index in range(lastChildCount):
375 child = AccessEngineAPI.getChildAcc(index, lastChild)
376 if (not AccessEngineAPI.hasAccRole('push button', child)):
377 return False
378 else:
379 return False
380
381
382
383
384
385
386
387 firstChild = AccessEngineAPI.getChildAcc(0, rootPor)
388 if not (AccessEngineAPI.hasAccRole('icon', \
389 AccessEngineAPI.getAccFromPath(firstChild, 0,0,1)) \
390 and \
391 AccessEngineAPI.hasAccRole('label',
392 AccessEngineAPI.getAccFromPath(firstChild, 0,0,2)) \
393 and \
394 AccessEngineAPI.hasAccRole('icon',
395 AccessEngineAPI.getAccFromPath(firstChild, 0,0,3)) \
396 and \
397 AccessEngineAPI.hasAccRole('label',
398 AccessEngineAPI.getAccFromPath(firstChild, 0,0,0,1,0))):
399 return False
400
401 return True
402
404 '''
405 Find the active panel in the Evolution Setup Assistant.
406
407 The active panel has the state 'showing' and is a dircet child of the
408 L{saRootPOR} instance variable. Sets the L{saActiveChild}
409 instance variable.
410 '''
411 found = False
412 index = 0
413 if self.saRootPOR:
414 childCount = AccessEngineAPI.getAccCount(self.saRootPOR) - 1
415
416 while not found and index < childCount:
417 childPor = AccessEngineAPI.getChildAcc(index, self.saRootPOR)
418 if AccessEngineAPI.hasAccState('showing', childPor):
419 self.saActiveChild = childPor
420 found = True
421 else:
422 index = index + 1
423 if index == childCount: self.saActiveChild = None
424
426 '''
427 Checks whether the L{AEPor} at the given path is a label.
428
429 @return: The AEPor if it is a label; C{False} otherwise.
430 @rtype: L{AEPor} or C{None}
431 '''
432 labelPor = AccessEngineAPI.getAccFromPath(rootPor, *path)
433 if labelPor:
434 role = AccessEngineAPI.getAccRole(labelPor)
435 if role == 'label':
436 return labelPor
437 else:
438 return None
439 else:
440 return None
441
443 '''
444 Read the header label and the description label of the active panel in the
445 Evolution Setup Assitant.
446 '''
447
448 panelIndex = AccessEngineAPI.getAccIndex(self.saActiveChild)
449
450
451 label1Por = self._getLabel(self.saRootPOR, panelIndex,0,0,2)
452
453
454
455 label2Por = None
456 label2Por = self._getLabel(self.saRootPOR, panelIndex,0,0,0,1,0)
457 if not label2Por:
458 label2Por = self._getLabel(self.saRootPOR, panelIndex,0,0,0,0,0)
459 if not label2Por:
460 label2Por = self._getLabel(self.saRootPOR, panelIndex,0,0,0,0,0,0,0)
461
462
463 AccessEngineAPI.inhibitMayStop()
464 if label1Por:
465 AccessEngineAPI.sayInfo(self, text=AccessEngineAPI.getAccName(label1Por),
466 cap='audio', role='output', **kwargs)
467 if label2Por:
468 AccessEngineAPI.sayInfo(self, text=AccessEngineAPI.getAccName(label2Por),
469 cap='audio', role='output', **kwargs)
470
471
473 self._findSAActivePanel()
474 panelIndex = AccessEngineAPI.getAccIndex(self.saActiveChild)
475 headerPor = self._getLabel(self.saRootPOR, panelIndex,0,0,2)
476 if headerPor:
477 text = AccessEngineAPI.getAccName(headerPor)
478
479 if text == _("Aus Sicherungsdatei wiederherstellen"):
480 if AccessEngineAPI.hasAccRole('push button', kwargs['por']):
481 text = AccessEngineAPI.getAccName(kwargs['por'])
482 if (not (text == _("Vor") or
483 text == _("Zurück") or
484 text == _("Abbrechen") or
485 text == _("Anwenden") or
486 text == _("Hilfe")
487 )):
488 text = AccessEngineAPI.getAccName(
489 AccessEngineAPI.getAccFromPath(self.saRootPOR,
490 panelIndex,0,0,0,0,0,2,0))
491 AccessEngineAPI.sayLabel(self, cap='audio', role='output',
492 text=text, **kwargs)
493 return True
494 return False
495
496
497
498
500 '''
501 Finds out, how many mails are shown in the message tree.
502
503 @Todo: RB: must still be implemented
504 '''
505 pass
506
508 '''
509 Read one cell of the table tree that contains the list of E-Mails.
510
511 @param columnHeader: the name of the column that contains the table cell
512 @type columnHeader: string
513 @param cellPOR: the table cell that shall be read
514 @type cellPOR: L{AEPor}
515 @param kwargs: Arbitrary keyword arguments to pass to the task
516 @type kwargs: dictionary
517 '''
518
519 text = None
520
521
522
523
524 if (columnHeader == _("Status")):
525 if (AccessEngineAPI.hasAccState('checked', cellPOR)):
526 text = _("read")
527 else:
528 text = _("unread")
529
530
531
532
533 elif (AccessEngineAPI.hasAccActionName(_("toggle"), cellPOR)):
534 if (AccessEngineAPI.hasAccState('checked', cellPOR)):
535 text = columnHeader
536
537
538
539 else:
540 text = columnHeader + " " + AccessEngineAPI.getAccName(cellPOR)
541
542 if text:
543 AccessEngineAPI.sayInfo(self, text=text, cap='audio', role='output',
544 **kwargs)
545
579
580
581
582
583
584
585
586
602
612
613 - def readMailBody(self, **kwargs):
614 '''
615 Reads the text of the whole e-mail.
616 '''
617 if self.htmlPanel:
618 AccessEngineAPI.sayInfo(self, cap='audio', role='output',
619 text=_('Mail Text: '), **kwargs)
620 for c in range(AccessEngineAPI.getAccCount(self.htmlPanel)):
621 child = AccessEngineAPI.getChildAcc(c, self.htmlPanel)
622 if (AccessEngineAPI.hasAccRole('unknown', child)):
623 self._readAllText(child, **kwargs)
624 break
625
626 - def _readAllText(self, ePor, **kwargs):
627 '''
628 Reads all text accessibles under the given por.
629
630 This method works recursivly.
631
632 @param ePor: the por which is used to find text accessibles.
633 @type ePor: L{AEPor}
634 '''
635 childCount = AccessEngineAPI.getAccCount(ePor)
636 if (childCount > 0):
637 for c in range(childCount):
638 self._readAllText(AccessEngineAPI.getChildAcc(c, ePor), **kwargs)
639 elif (AccessEngineAPI.hasAccRole('text', ePor)):
640
641
642 kwargs['text'] = AccessEngineAPI.getAccText(ePor)
643
644
645 AccessEngineAPI.inhibitMayStop()
646 self.readEMail(**kwargs)
647
648 - def _quote(self, text, **kwargs):
649 '''
650 Reads quotation in an e-mail.
651 '''
652
653 numMarks = 0
654 for s in text.split('>'):
655 s = s.strip()
656 if s == '':
657 numMarks = numMarks + 1
658 else:
659 break
660 if (numMarks == len(text.split('>'))): numMarks = numMarks - 1
661
662 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs)
663 if (self.lastQuotationLevel is not numMarks):
664 level = _('Quotation Level')
665 level = '%s: %d ' % (level, numMarks)
666 self.lastQuotationLevel = numMarks
667 AccessEngineAPI.sayLevel(self, cap='audio', role='output', text=level, **kwargs)
668 AccessEngineAPI.sayLevel(self, cap='audio', role='output', text=text, **kwargs)
669
671 '''
672 Read a row in a mail.
673 '''
674 mainPOR = AccessEngineAPI.findAccByName(_('Panel containing HTML'), kwargs['por'],
675 ancestor = True)
676 if mainPOR:
677 try:
678 if (kwargs['text'].startswith('>')):
679 self._quote(**kwargs)
680 else:
681 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs)
682 except KeyError:
683 text = AccessEngineAPI.getItemText(kwargs['por'])
684 label = AccessEngineAPI.getAccLabel(kwargs['por'])
685 if (label != text):
686 if (text.startswith('>')):
687 self._quote(text, **kwargs)
688 else:
689 self.lastQuotationLevel = 0
690 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs)
691 AccessEngineAPI.sayItem(self, text=text, cap='audio', role='output', **kwargs)
692 else:
693 try:
694 if (kwargs['text'].startswith('>')):
695 self._quote(**kwargs)
696 else:
697 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs)
698 except KeyError:
699 self.unchainTask('read email', AEConstants.CHAIN_AROUND,
700 'read item text', 'BasicSpeechScript')
701 self.doTask('read item text', 'BasicSpeechScript', **kwargs)
702 self.chainTask('read email', AEConstants.CHAIN_AROUND,
703 'read item text', 'BasicSpeechScript')
704
705
706
707
708