1 '''
2 Defines a special L{AEScript <AEScript.AEScript>} for the text editor gedit.
3 Handle several problems, including mainly the spellcheck dialogue and the
4 document statistics dialogue.
5
6 @author: Andy Shi
7 @author: Cristobal Palmer
8 @author: Peter Parente
9 @organization: UNC Chapel Hill
10 @copyright: Copyright (c) 2007, Andy Shi, Cristobal Palmer
11
12 @author: Nicole Anacker
13 @organization: IT Science Center Ruegen gGmbH, Germany
14 @copyright: Copyright (c) 2007, 2008 ITSC Ruegen
15
16 @license: I{The BSD License}.
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD License which is available at
19 U{http://www.opensource.org/licenses/bsd-license.php}
20
21 @see: U{http://www.unc.edu/campus/policies/copyright.html} Section 5.D.2.A
22 for UNC copyright rules.
23 '''
24
25 from AccessEngine import AEScript, AccessEngineAPI
26 from AccessEngine import AEConstants
27 from Tools.i18n import bind, _
28
29
30 __uie__ = dict(kind='script', tier='gedit', all_tiers=False)
31
33 '''
34 A special L{AEScript <AEScript.AEScript>} for gedit.
35 This class register tasks that handle the check spelling dialog and document
36 statistics dialog. It also defines hotkeys.
37 I{Caps-Lock+K} reads the current misspelled word in the context of the spell
38 check dialog.
39 I{Caps-Lock+I} reads the line containing the misspelled word.
40
41 @ivar context: Context of the current cursor position.
42 @type context: string
43 @ivar spelling: Is the spell check dialog active?
44 @type spelling: boolean
45 '''
47 '''
48 Registers L{event_tasks <AEScript.event_tasks>} to handle
49 L{focus <AEEvent.FocusChange>}, L{caret <AEEvent.CaretChange>} and
50 L{property change <AEEvent.PropertyChange>} events. Registers
51 L{tasks <AEScript.registered_tasks>} that can be mapped to
52 L{AEInput.Gesture}s.
53 '''
54 self.context = None
55 self.spelling = False
56
57
58 AccessEngineAPI.setScriptIdealOutput(self, 'audio')
59
60
61 self.registerEventTask('track focus', AEConstants.EVENT_TYPE_FOCUS_CHANGE,
62 tier=True, background=True)
63 self.registerEventTask('track carets', AEConstants.EVENT_TYPE_CARET_CHANGE,
64 tier=True)
65
66
67
68
69
70
71 self.registerTask('read statistic', self.readStatisticDialog)
72 self.registerTask('read spelling', self.readSpellingDialog)
73 self.chainTask('read statistic', AEConstants.CHAIN_AFTER,
74 'read dialog', 'BasicSpeechScript')
75 self.chainTask('read spelling', AEConstants.CHAIN_AFTER,
76 'read view', 'BasicSpeechScript')
77
78
79 self.registerTask('read misspelled word', self.readMisspelledWord)
80 self.registerTask('read misspelled context', self.readMisspelledContext)
81
82 kbd = AccessEngineAPI.getInputDevice(None, 'keyboard')
83 AccessEngineAPI.addInputModifiers(self, kbd, kbd.AEK_CAPS_LOCK)
84 self.registerCommand(kbd, 'read misspelled word',
85 _('read misspelled word'),
86 False, [kbd.AEK_CAPS_LOCK, kbd.AEK_K])
87 self.registerCommand(kbd, 'read misspelled context',
88 _('read misspelled context'),
89 False, [kbd.AEK_CAPS_LOCK, kbd.AEK_I])
90
92 '''
93 Describe which L{AETier} this script applies to by default.
94
95 @return: Human readable translated description of this script.
96 @rtype: string
97 '''
98 return _('Improves the usability of gedit spell check and document '
99 'statistics dialogs.')
100
102 '''
103 Check if por is in the document.
104
105 @param por: Point of regard for the related accessible
106 @type por: L{AEPor}
107 @return: C{True} if por is in the document.
108 @rtype: boolean
109 '''
110 return AccessEngineAPI.hasAccRole('text', por) and \
111 AccessEngineAPI.hasAccState('multi line', por)
112
113
114
115
117 '''
118 Check if por is in the statistic dialog.
119
120 @param por: Point of regard for the related accessible
121 @type por: L{AEPor}
122 @return: C{True} if por is in the statistic dialog, C{False} if not.
123 @rtype: boolean
124 '''
125
126
127 lblOne = AccessEngineAPI.getAccFromPath(por, 0,0,0)
128 lblTwo = AccessEngineAPI.getAccFromPath(por, 0,0,1,1,2)
129 lblThree = AccessEngineAPI.getAccFromPath(por, 0,0,1,1,3)
130 if not (AccessEngineAPI.hasAccRole('label', lblOne) and
131 AccessEngineAPI.hasAccRole('label', lblTwo) and
132 AccessEngineAPI.hasAccRole('label', lblThree)):
133 return False
134 return True
135
137 '''
138 Read all information on the statistic dialog in a sensible order.
139
140 @param rootPor: Point of regard for the related accessible
141 @type rootPor: L{AEPor}
142 @param kwargs: Arbitrary keyword arguments to pass to the task
143 @type kwargs: dictionary
144 '''
145 item_list = []
146
147 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,2))
148 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,1))
149 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,2,1))
150
151 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,3))
152 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,4))
153 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,2,2))
154
155 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,6))
156 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,5))
157 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,2,3))
158
159 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,8))
160 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,7))
161 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,2,4))
162
163 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,10))
164 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,1,9))
165 item_list.append(AccessEngineAPI.getAccFromPath(rootPor, 0,0,1,2,5))
166 for i in xrange(0, len(item_list), 3):
167 AccessEngineAPI.sayLabel(self, cap='audio', role='output',
168 text=AccessEngineAPI.getAccName(item_list[i]),
169 talk=False, **kwargs)
170 AccessEngineAPI.sayItem(self, cap='audio', role='output',
171 text=AccessEngineAPI.getItemText(item_list[i+1]),
172 talk=True, **kwargs)
173 AccessEngineAPI.sayItem(self, cap='audio', role='output',
174 text=AccessEngineAPI.getItemText(item_list[i+2]),
175 talk=False, **kwargs)
176
178 '''
179 Reads the documents statistics dialog.
180
181 @param kwargs: Arbitrary keyword arguments to pass to the task
182 @type kwargs: dictionary
183
184 @todo: we should say the labels as row and column headers, at least for
185 the first row
186 '''
187 por = AccessEngineAPI.getRootAcc(kwargs['por'])
188 if self._isStatistic(por):
189 self._sayStatistic(por, **kwargs)
190
191
192
193
195 '''
196 Check if por is in the spelling dialog.
197
198 @param por: Point of regard for the related accessible
199 @type por: L{AEPor}
200 @return: C{True} if por in the spelling dialog, C{False} if not.
201 @rtype: boolean
202 '''
203
204 checkLabel = AccessEngineAPI.getAccFromPath(por, 0,0,1)
205 checkButton = AccessEngineAPI.getAccFromPath(por, 0,0,0,1)
206 checkTextField = AccessEngineAPI.getAccFromPath(por, 0,0,0,0)
207 if not (AccessEngineAPI.hasAccRole('label', checkLabel) and
208 AccessEngineAPI.hasAccRole('push button', checkButton) and
209 AccessEngineAPI.hasAccRole('text', checkTextField)):
210
211 self.spelling = False
212 return False
213 self.spelling = True
214 return True
215
217 '''
218 Read the misspelled word.
219
220 @param por: Point of regard for the related accessible
221 @type por: L{AEPor}
222 '''
223
224 label_por = AccessEngineAPI.getAccFromPath(por, 0,0,3)
225 label_text = AccessEngineAPI.getAccName(label_por)
226
227 word_por = AccessEngineAPI.getAccFromPath(por, 0,0,2)
228 word_text = AccessEngineAPI.getAccName(word_por)
229
230 AccessEngineAPI.inhibitMayStop()
231 AccessEngineAPI.say(self, cap='audio', role='output', text=label_text,
232 layer=AEConstants.LAYER_FOCUS, sem=AEConstants.SEM_LABEL)
233 AccessEngineAPI.say(self, cap='audio', role='output', text=word_text,
234 layer=AEConstants.LAYER_FOCUS, sem=AEConstants.SEM_WORD)
235
237 '''
238 Reads the misspelled word in the spelling dialog when it first appears.
239
240 @param kwargs: Arbitrary keyword arguments to pass to the task
241 @type kwargs: dictionary
242 '''
243 if kwargs['gained']:
244 if self._isSpelling(kwargs['por']):
245 self._saySpelling(kwargs['por'])
246
248 '''
249 Reads the misspelled word whenever the user presses a hotkey. Because the
250 first misspelled word is not highlighted by gedit, getAccSelection cannot be
251 used to retrieve the misspelled word. The only way to get to the misspelled
252 word is by accessing the misspelled word label to explicitly extract its
253 contents.
254
255 @param kwargs: Arbitrary keyword arguments to pass to the task
256 @type kwargs: dictionary
257 '''
258 if not self.spelling:
259
260 return
261
262 root = AccessEngineAPI.getRootAcc(kwargs['por'])
263
264 misspelled_word_por = AccessEngineAPI.getAccFromPath(root, 0,0,2)
265
266 misspelled_word_text = AccessEngineAPI.getAccName(misspelled_word_por)
267
268 self.doTask('cycle review word', 'BasicSpeechScript',
269 text=misspelled_word_text, **kwargs)
270
271 - def readMisspelledContext(self, **kwargs):
272 '''
273 Reads the line containing the misspelled word. Raises pitch on the
274 misspelled word if that feature is supported.
275
276 @param kwargs: Arbitrary keyword arguments to pass to the task
277 @type kwargs: dictionary
278 '''
279 if not self.spelling:
280
281 return
282 por = self.context
283 if por is None:
284
285 return
286
287 text = AccessEngineAPI.getItemText(por)
288
289 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs)
290 try:
291
292 sett = AccessEngineAPI.getStyleSetting(self, 'Pitch',
293 sem=AEConstants.SEM_WORD, **kwargs)
294 except Task.InvalidStyleError:
295 AccessEngineAPI.sayItem(self, cap='audio', role='output',
296 text=text, **kwargs)
297 else:
298
299 prefix = text[:por.char_offset]
300 if prefix.strip():
301 AccessEngineAPI.sayItem(self, cap='audio', role='output', text=prefix,
302 talk=False, **kwargs)
303
304
305 diff = (sett.max - sett.min)/10
306 sett.value += diff
307 word = AccessEngineAPI.getWordText(por)
308 AccessEngineAPI.sayWord(self, cap='audio', role='output',
309 text=word, talk=False, **kwargs)
310
311 sett.value -= diff
312
313 suffix = text[por.char_offset+len(word):]
314 if suffix:
315 AccessEngineAPI.sayItem(self, cap='audio', role='output',
316 text=suffix, **kwargs)
317 return True
318
319
320
321
323 '''
324 Keeps track of the L{AEPor} to the misspelled word.
325
326 @param kwargs: Arbitrary keyword arguments to pass to the task
327 @type kwargs: dictionary
328 '''
329 if not self._isDocument(kwargs['por']):
330
331 return
332
333 self.context = AccessEngineAPI.getAccCaret(kwargs['por'])
334
336 '''
337 Keeps track of the L{AEPor} to the misspelled word. Announces it when the
338 spelling dialog is not shown.
339
340 @param kwargs: Arbitrary keyword arguments to pass to the task
341 @type kwargs: dictionary
342 '''
343 if not self._isDocument(kwargs['por']):
344
345 return
346
347 self.context = kwargs['por']
348 if self.spelling:
349
350 AccessEngineAPI.inhibitMayStop()
351 AccessEngineAPI.inhibitMayStop()
352 AccessEngineAPI.say(self, cap='audio', role='output',
353 text=_('misspelled word'), sem=AEConstants.SEM_LABEL,
354 layer=AEConstants.LAYER_FOCUS)
355 AccessEngineAPI.say(self, cap='audio', role='output',
356 text=AccessEngineAPI.getWordText(kwargs['por']),
357 sem=AEConstants.SEM_WORD,
358 layer=AEConstants.LAYER_FOCUS)
359
361 '''
362 Monitors state changes on the table to determine if a word is misspelled or
363 correct.
364
365 @param kwargs: Arbitrary keyword arguments to pass to the task
366 @type kwargs: dictionary
367
368 @todo: needs work, what about the case where the table doesn't change state?
369 '''
370
371 if not self.spelling:
372 return
373
374 root = AccessEngineAPI.getRootAcc(kwargs['por'])
375 table = AccessEngineAPI.getAccFromPath(root, 0,1,3,0)
376 if (table == por and name == 'sensitive'):
377 if value:
378 AccessEngineAPI.sayInfo(self, cap='audio', role='output',
379 text=_('misspelled'), **kwargs)
380 else:
381 AccessEngineAPI.sayInfo(self, cap='audio', role='output',
382 text=_('spelled correctly'), **kwargs)
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397