1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2005-2006 Sun Microsystems Inc. |
4 |
|
# |
5 |
|
# This library is free software; you can redistribute it and/or |
6 |
|
# modify it under the terms of the GNU Library General Public |
7 |
|
# License as published by the Free Software Foundation; either |
8 |
|
# version 2 of the License, or (at your option) any later version. |
9 |
|
# |
10 |
|
# This library is distributed in the hope that it will be useful, |
11 |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 |
|
# Library General Public License for more details. |
14 |
|
# |
15 |
|
# You should have received a copy of the GNU Library General Public |
16 |
|
# License along with this library; if not, write to the |
17 |
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 |
|
# Boston, MA 02111-1307, USA. |
19 |
|
|
20 |
1 |
"""Custom script for StarOffice and OpenOffice.""" |
21 |
|
|
22 |
1 |
__id__ = "$Id: StarOffice.py 2126 2007-03-06 21:35:17Z richb $" |
23 |
1 |
__version__ = "$Revision: 2126 $" |
24 |
1 |
__date__ = "$Date: 2007-03-06 13:35:17 -0800 (Tue, 06 Mar 2007) $" |
25 |
1 |
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc." |
26 |
1 |
__license__ = "LGPL" |
27 |
|
|
28 |
1 |
import orca.debug as debug |
29 |
1 |
import orca.atspi as atspi |
30 |
1 |
import orca.chnames as chnames |
31 |
1 |
import orca.default as default |
32 |
1 |
import orca.input_event as input_event |
33 |
1 |
import orca.rolenames as rolenames |
34 |
1 |
import orca.braille as braille |
35 |
1 |
import orca.braillegenerator as braillegenerator |
36 |
1 |
import orca.orca as orca |
37 |
1 |
import orca.orca_state as orca_state |
38 |
1 |
import orca.speech as speech |
39 |
1 |
import orca.speechgenerator as speechgenerator |
40 |
1 |
import orca.settings as settings |
41 |
1 |
import orca.keybindings as keybindings |
42 |
|
|
43 |
1 |
from orca.orca_i18n import _ # for gettext support |
44 |
|
|
45 |
1 |
inputLineForCell = None |
46 |
|
|
47 |
|
# Dictionaries for the calc dynamic row and column headers. |
48 |
|
# |
49 |
1 |
dynamicColumnHeaders = {} |
50 |
1 |
dynamicRowHeaders = {} |
51 |
|
|
52 |
1 |
def adjustForWriterTable(obj): |
53 |
|
"""Check to see if we are in Writer, where the object with focus is a |
54 |
|
paragraph, and the parent is the table cell. If it is, then, return the |
55 |
|
parent table cell otherwise return the current object. |
56 |
|
|
57 |
|
Arguments: |
58 |
|
- obj: the accessible object to check. |
59 |
|
|
60 |
|
Returns parent table cell (if in a Writer table ) or the current object. |
61 |
|
""" |
62 |
|
|
63 |
11 |
if obj.role == rolenames.ROLE_PARAGRAPH and \ |
64 |
|
obj.parent.role == rolenames.ROLE_TABLE_CELL: |
65 |
0 |
return obj.parent |
66 |
|
else: |
67 |
11 |
return obj |
68 |
|
|
69 |
1 |
def getTable(obj): |
70 |
|
"""Get the table that this table cell is in. |
71 |
|
|
72 |
|
Arguments: |
73 |
|
- obj: the table cell. |
74 |
|
|
75 |
|
Return the table that this table cell is in, or None if this object |
76 |
|
isn't in a table. |
77 |
|
""" |
78 |
|
|
79 |
11 |
table = None |
80 |
11 |
obj = adjustForWriterTable(obj) |
81 |
11 |
if obj.role == rolenames.ROLE_TABLE_CELL and obj.parent: |
82 |
11 |
table = obj.parent.table |
83 |
|
|
84 |
11 |
return table |
85 |
|
|
86 |
1 |
def getDynamicColumnHeaderCell(obj, column): |
87 |
|
"""Given a table cell, return the dynamic column header cell associated |
88 |
|
with it. |
89 |
|
|
90 |
|
Arguments: |
91 |
|
- obj: the table cell. |
92 |
|
- column: the column that this dynamic header is on. |
93 |
|
|
94 |
|
Return the dynamic column header cell associated with the given table cell. |
95 |
|
""" |
96 |
|
|
97 |
0 |
obj = adjustForWriterTable(obj) |
98 |
0 |
accCell = None |
99 |
0 |
parent = obj.parent |
100 |
0 |
if parent and parent.table: |
101 |
0 |
row = parent.table.getRowAtIndex(obj.index) |
102 |
0 |
cell = parent.table.getAccessibleAt(row, column) |
103 |
0 |
accCell = atspi.Accessible.makeAccessible(cell) |
104 |
|
|
105 |
0 |
return accCell |
106 |
|
|
107 |
1 |
def getDynamicRowHeaderCell(obj, row): |
108 |
|
"""Given a table cell, return the dynamic row header cell associated |
109 |
|
with it. |
110 |
|
|
111 |
|
Arguments: |
112 |
|
- obj: the table cell. |
113 |
|
- row: the row that this dynamic header is on. |
114 |
|
|
115 |
|
Return the dynamic row header cell associated with the given table cell. |
116 |
|
""" |
117 |
|
|
118 |
0 |
obj = adjustForWriterTable(obj) |
119 |
0 |
accCell = None |
120 |
0 |
parent = obj.parent |
121 |
0 |
if parent and parent.table: |
122 |
0 |
column = parent.table.getColumnAtIndex(obj.index) |
123 |
0 |
cell = parent.table.getAccessibleAt(row, column) |
124 |
0 |
accCell = atspi.Accessible.makeAccessible(cell) |
125 |
|
|
126 |
0 |
return accCell |
127 |
|
|
128 |
1 |
def locateInputLine(obj): |
129 |
|
"""Return the spread sheet input line. This only needs to be found |
130 |
|
the very first time a spread sheet table cell gets focus. We use the |
131 |
|
table cell to work back up the component hierarchy until we have found |
132 |
|
the common panel that both it and the input line reside in. We then |
133 |
|
use that as the base component to search for a component which has a |
134 |
|
paragraph role. This will be the input line. |
135 |
|
|
136 |
|
Arguments: |
137 |
|
- obj: the spread sheet table cell that has just got focus. |
138 |
|
|
139 |
|
Returns the spread sheet input line component. |
140 |
|
""" |
141 |
|
|
142 |
0 |
inputLine = None |
143 |
0 |
panel = obj.parent.parent.parent.parent |
144 |
0 |
if panel and panel.role == rolenames.ROLE_PANEL: |
145 |
0 |
allParagraphs = self.findByRole(panel, rolenames.ROLE_PARAGRAPH) |
146 |
0 |
if len(allParagraphs) == 1: |
147 |
0 |
inputLine = allParagraphs[0] |
148 |
|
else: |
149 |
0 |
debug.println(debug.LEVEL_SEVERE, |
150 |
0 |
"StarOffice: locateInputLine: incorrect paragraph count.") |
151 |
|
else: |
152 |
0 |
debug.println(debug.LEVEL_SEVERE, |
153 |
0 |
"StarOffice: locateInputLine: couldn't find common panel.") |
154 |
|
|
155 |
0 |
return inputLine |
156 |
|
|
157 |
1 |
def isSpreadSheetCell(obj): |
158 |
|
"""Return an indication of whether the given obj is a spread sheet |
159 |
|
table cell. |
160 |
|
|
161 |
|
Arguments: |
162 |
|
- obj: the object to check. |
163 |
|
|
164 |
|
Returns True if this is a table cell, False otherwise. |
165 |
|
""" |
166 |
|
|
167 |
11 |
found = False |
168 |
11 |
rolesList = [rolenames.ROLE_TABLE_CELL, \ |
169 |
|
rolenames.ROLE_TABLE, \ |
170 |
|
rolenames.ROLE_UNKNOWN, \ |
171 |
|
rolenames.ROLE_SCROLL_PANE, \ |
172 |
|
rolenames.ROLE_PANEL, \ |
173 |
|
rolenames.ROLE_ROOT_PANE, \ |
174 |
|
rolenames.ROLE_FRAME, \ |
175 |
|
rolenames.ROLE_APPLICATION] |
176 |
11 |
if self.isDesiredFocusedItem(obj, rolesList): |
177 |
|
# We've found a table cell with the correct hierarchy. Now check |
178 |
|
# that we are in a spreadsheet as opposed to the writer application. |
179 |
|
# See bug #382408. |
180 |
|
# |
181 |
0 |
current = obj.parent |
182 |
0 |
while current.role != rolenames.ROLE_APPLICATION: |
183 |
0 |
if current.role == rolenames.ROLE_FRAME and \ |
184 |
|
(current.name and current.name.endswith(_("Calc"))): |
185 |
0 |
found = True |
186 |
0 |
current = current.parent |
187 |
|
|
188 |
0 |
return found |
189 |
|
|
190 |
2 |
class BrailleGenerator(braillegenerator.BrailleGenerator): |
191 |
|
"""Overrides _getBrailleRegionsForTableCellRow so that , when we are |
192 |
|
in a spread sheet, we can braille the dynamic row and column headers |
193 |
|
(assuming they are set). |
194 |
|
Overrides _getBrailleRegionsForTableCell so that, when we are in |
195 |
|
a spread sheet, we can braille the location of the table cell as well |
196 |
|
as the contents. |
197 |
|
""" |
198 |
|
|
199 |
1 |
def __init__(self, script): |
200 |
4 |
braillegenerator.BrailleGenerator.__init__(self, script) |
201 |
|
|
202 |
1 |
def _getBrailleRegionsForTableCellRow(self, obj): |
203 |
|
"""Get the braille for a table cell row or a single table cell |
204 |
|
if settings.readTableCellRow is False. |
205 |
|
|
206 |
|
Arguments: |
207 |
|
- obj: the table cell |
208 |
|
|
209 |
|
Returns a list where the first element is a list of Regions to display |
210 |
|
and the second element is the Region which should get focus. |
211 |
|
""" |
212 |
|
|
213 |
0 |
global dynamicColumnHeaders, dynamicRowHeaders |
214 |
0 |
global getColumnHeaderCell, getRowHeaderCell, isSpreadSheetCell |
215 |
|
|
216 |
11 |
regions = [] |
217 |
|
|
218 |
|
# Check to see if this spread sheet cell has either a dynamic |
219 |
|
# column heading or row heading (or both) associated with it. |
220 |
|
# If it does, then braille those first before brailling the |
221 |
|
# cell contents. |
222 |
|
# |
223 |
11 |
table = getTable(obj) |
224 |
11 |
parent = obj.parent |
225 |
|
|
226 |
11 |
if parent.__dict__.has_key("lastColumn") and \ |
227 |
|
parent.lastColumn != parent.table.getColumnAtIndex(obj.index): |
228 |
0 |
if dynamicColumnHeaders.has_key(table): |
229 |
0 |
row = dynamicColumnHeaders[table] |
230 |
0 |
header = getDynamicRowHeaderCell(obj, row) |
231 |
0 |
if header.childCount > 0: |
232 |
0 |
for i in range(0, header.childCount): |
233 |
0 |
child = header.child(i) |
234 |
0 |
text = self._script.getText(child, 0, -1) |
235 |
0 |
if text: |
236 |
0 |
regions.append(braille.Region(" " + text + " ")) |
237 |
0 |
elif header.text: |
238 |
0 |
text = self._script.getText(header, 0, -1) |
239 |
0 |
if text: |
240 |
0 |
regions.append(braille.Region(" " + text + " ")) |
241 |
|
|
242 |
11 |
if parent.__dict__.has_key("lastRow") and \ |
243 |
|
parent.lastRow != parent.table.getRowAtIndex(obj.index): |
244 |
0 |
if dynamicRowHeaders.has_key(table): |
245 |
0 |
column = dynamicRowHeaders[table] |
246 |
0 |
header = getDynamicColumnHeaderCell(obj, column) |
247 |
0 |
if header.childCount > 0: |
248 |
0 |
for i in range(0, header.childCount): |
249 |
0 |
child = header.child(i) |
250 |
0 |
text = self._script.getText(child, 0, -1) |
251 |
0 |
if text: |
252 |
0 |
regions.append(braille.Region(" " + text + " ")) |
253 |
0 |
elif header.text: |
254 |
0 |
text = self._script.getText(header, 0, -1) |
255 |
0 |
if text: |
256 |
0 |
regions.append(braille.Region(" " + text + " ")) |
257 |
|
|
258 |
11 |
if isSpreadSheetCell(obj): |
259 |
|
|
260 |
|
# Adding in a check here to make sure that the parent is a |
261 |
|
# valid table. It's possible that the parent could be a |
262 |
|
# table cell too (see bug #351501). |
263 |
|
# |
264 |
0 |
if settings.readTableCellRow and obj.parent.table: |
265 |
0 |
rowRegions = [] |
266 |
0 |
savedBrailleVerbosityLevel = settings.brailleVerbosityLevel |
267 |
0 |
settings.brailleVerbosityLevel = \ |
268 |
|
settings.VERBOSITY_LEVEL_BRIEF |
269 |
|
|
270 |
0 |
parent = obj.parent |
271 |
0 |
row = parent.table.getRowAtIndex(obj.index) |
272 |
0 |
column = parent.table.getColumnAtIndex(obj.index) |
273 |
|
|
274 |
|
# This is an indication of whether we should speak all the |
275 |
|
# table cells (the user has moved focus up or down a row), |
276 |
|
# or just the current one (focus has moved left or right in |
277 |
|
# the same row). |
278 |
|
# |
279 |
0 |
speakAll = True |
280 |
0 |
if parent.__dict__.has_key("lastRow") and \ |
281 |
|
parent.__dict__.has_key("lastColumn"): |
282 |
0 |
speakAll = (parent.lastRow != row) or \ |
283 |
|
((row == 0 or row == parent.table.nRows-1) and \ |
284 |
|
parent.lastColumn == column) |
285 |
|
|
286 |
0 |
if speakAll: |
287 |
0 |
focusRowRegion = None |
288 |
0 |
for i in range(0, parent.table.nColumns): |
289 |
0 |
accRow = parent.table.getAccessibleAt(row, i) |
290 |
0 |
cell = atspi.Accessible.makeAccessible(accRow) |
291 |
0 |
showing = cell.state.count( \ |
292 |
|
atspi.Accessibility.STATE_SHOWING) |
293 |
0 |
if showing: |
294 |
0 |
[cellRegions, focusRegion] = \ |
295 |
|
self._getBrailleRegionsForTableCell(cell) |
296 |
0 |
if len(rowRegions): |
297 |
0 |
rowRegions.append(braille.Region(" ")) |
298 |
0 |
rowRegions.append(cellRegions[0]) |
299 |
0 |
if i == column: |
300 |
0 |
focusRowRegion = cellRegions[0] |
301 |
0 |
regions.extend(rowRegions) |
302 |
0 |
settings.brailleVerbosityLevel = savedBrailleVerbosityLevel |
303 |
|
else: |
304 |
0 |
[cellRegions, focusRegion] = \ |
305 |
|
self._getBrailleRegionsForTableCell(obj) |
306 |
0 |
regions.extend(cellRegions) |
307 |
|
else: |
308 |
0 |
[cellRegions, focusRegion] = \ |
309 |
|
self._getBrailleRegionsForTableCell(obj) |
310 |
0 |
regions.extend(cellRegions) |
311 |
0 |
regions = [regions, focusRegion] |
312 |
|
else: |
313 |
0 |
brailleGen = braillegenerator.BrailleGenerator |
314 |
0 |
[cellRegions, focusRegion] = \ |
315 |
|
brailleGen._getBrailleRegionsForTableCellRow(self, obj) |
316 |
0 |
regions.extend(cellRegions) |
317 |
0 |
regions = [regions, focusRegion] |
318 |
|
|
319 |
0 |
return regions |
320 |
|
|
321 |
1 |
def _getBrailleRegionsForTableCell(self, obj): |
322 |
|
"""Get the braille for a table cell. If this isn't inside a |
323 |
|
spread sheet, just return the regions returned by the default |
324 |
|
table cell braille handler. |
325 |
|
|
326 |
|
Arguments: |
327 |
|
- obj: the table cell |
328 |
|
|
329 |
|
Returns a list where the first element is a list of Regions to display |
330 |
|
and the second element is the Region which should get focus. |
331 |
|
""" |
332 |
|
|
333 |
0 |
global inputLineForCell, isSpreadSheetCell, locateInputLine |
334 |
|
|
335 |
0 |
if isSpreadSheetCell(obj): |
336 |
0 |
if inputLineForCell == None: |
337 |
0 |
inputLineForCell = locateInputLine(obj) |
338 |
|
|
339 |
0 |
regions = [] |
340 |
0 |
text = self.getDisplayedText(obj) |
341 |
0 |
componentRegion = braille.Component(obj, text) |
342 |
0 |
regions.append(componentRegion) |
343 |
|
|
344 |
|
# If the spread sheet table cell has something in it, then we |
345 |
|
# want to append the name of the cell (which will be its location). |
346 |
|
# Note that if the cell was empty, then self.getDisplayedText will |
347 |
|
# have already done this for us. |
348 |
|
# |
349 |
0 |
if obj.text: |
350 |
0 |
objectText = self._script.getText(obj, 0, -1) |
351 |
0 |
if objectText and len(objectText) != 0: |
352 |
0 |
regions.append(braille.Region(" " + obj.name)) |
353 |
|
|
354 |
0 |
return [regions, componentRegion] |
355 |
|
|
356 |
|
else: |
357 |
|
# Check to see how many children this table cell has. If it's |
358 |
|
# just one (or none), then pass it on to the superclass to be |
359 |
|
# processed. |
360 |
|
# |
361 |
|
# If it's more than one, then get the braille regions for each |
362 |
|
# child, and call this method again. |
363 |
|
# |
364 |
0 |
if obj.childCount <= 1: |
365 |
0 |
brailleGen = braillegenerator.BrailleGenerator |
366 |
0 |
regions = brailleGen._getBrailleRegionsForTableCell(self, obj) |
367 |
|
else: |
368 |
0 |
regions = [] |
369 |
0 |
for i in range(0, obj.childCount): |
370 |
0 |
child = obj.child(i) |
371 |
0 |
[cellRegions, focusRegion] = \ |
372 |
|
self._getBrailleRegionsForTableCell(child) |
373 |
0 |
regions.extend(cellRegions) |
374 |
0 |
return [regions, focusRegion] |
375 |
|
|
376 |
0 |
return regions |
377 |
|
|
378 |
2 |
class SpeechGenerator(speechgenerator.SpeechGenerator): |
379 |
|
"""Overrides _getSpeechForComboBox so that we can provide a name for |
380 |
|
the Calc Name combo box. |
381 |
|
Overrides _getSpeechForTableCellRow so that , when we are in a |
382 |
|
spread sheet, we can speak the dynamic row and column headers |
383 |
|
(assuming they are set). |
384 |
|
Overrides _getSpeechForTableCell so that, when we are in a spread |
385 |
|
sheet, we can speak the location of the table cell as well as the |
386 |
|
contents. |
387 |
|
Overrides _getSpeechForToggleButton so that, when the toggle buttons |
388 |
|
on the Formatting toolbar change state, we provide both the name and |
389 |
|
the state (as "on" or "off") |
390 |
|
Overrides _getSpeechForPushButton because sometimes the toggle buttons |
391 |
|
on the Formatting toolbar claim to be push buttons. |
392 |
|
""" |
393 |
1 |
def __init__(self, script): |
394 |
4 |
speechgenerator.SpeechGenerator.__init__(self, script) |
395 |
|
|
396 |
1 |
def _getSpeechForComboBox(self, obj, already_focused): |
397 |
|
"""Get the speech for a combo box. If the combo box already has focus, |
398 |
|
then only the selection is spoken. |
399 |
|
Also provides a name for the OOo Calc Name combo box. This name is |
400 |
|
provided in clause 5) of locusOfFocusChanged() below. |
401 |
|
|
402 |
|
Arguments: |
403 |
|
- obj: the combo box |
404 |
|
- already_focused: False if object just received focus |
405 |
|
|
406 |
|
Returns a list of utterances to be spoken for the object. |
407 |
|
""" |
408 |
|
|
409 |
2 |
utterances = [] |
410 |
|
|
411 |
2 |
if not already_focused: |
412 |
2 |
label = self._getSpeechForObjectLabel(obj) |
413 |
2 |
if not label: |
414 |
2 |
label = [ obj.name ] |
415 |
2 |
utterances.extend(label) |
416 |
|
else: |
417 |
0 |
label = None |
418 |
|
|
419 |
2 |
name = self._getSpeechForObjectName(obj) |
420 |
2 |
if name != label: |
421 |
0 |
utterances.extend(name) |
422 |
|
|
423 |
2 |
if not already_focused: |
424 |
2 |
utterances.extend(self._getSpeechForObjectRole(obj)) |
425 |
|
|
426 |
2 |
utterances.extend(self._getSpeechForObjectAvailability(obj)) |
427 |
|
|
428 |
2 |
self._debugGenerator("_getSpeechForComboBox", |
429 |
2 |
obj, |
430 |
2 |
already_focused, |
431 |
2 |
utterances) |
432 |
|
|
433 |
2 |
return utterances |
434 |
|
|
435 |
1 |
def _getSpeechForTableCellRow(self, obj, already_focused): |
436 |
|
"""Get the speech for a table cell row or a single table cell |
437 |
|
if settings.readTableCellRow is False. If this isn't inside a |
438 |
|
spread sheet, just return the utterances returned by the default |
439 |
|
table cell speech handler. |
440 |
|
|
441 |
|
Arguments: |
442 |
|
- obj: the table cell |
443 |
|
- already_focused: False if object just received focus |
444 |
|
|
445 |
|
Returns a list of utterances to be spoken for the object. |
446 |
|
""" |
447 |
|
|
448 |
0 |
global dynamicColumnHeaders, dynamicRowHeaders |
449 |
0 |
global getColumnHeaderCell, getRowHeaderCell, isSpreadSheetCell |
450 |
|
|
451 |
0 |
utterances = [] |
452 |
|
|
453 |
0 |
if not already_focused: |
454 |
|
|
455 |
|
# Check to see if this spread sheet cell has either a dynamic |
456 |
|
# column heading or row heading (or both) associated with it. |
457 |
|
# If it does, then speak those first before speaking the cell |
458 |
|
# contents. |
459 |
|
# |
460 |
0 |
table = getTable(obj) |
461 |
0 |
parent = obj.parent |
462 |
|
|
463 |
0 |
if parent.__dict__.has_key("lastColumn") and \ |
464 |
|
parent.lastColumn != \ |
465 |
|
parent.table.getColumnAtIndex(obj.index): |
466 |
0 |
if dynamicColumnHeaders.has_key(table): |
467 |
0 |
row = dynamicColumnHeaders[table] |
468 |
0 |
header = getDynamicRowHeaderCell(obj, row) |
469 |
0 |
if header.childCount > 0: |
470 |
0 |
for i in range(0, header.childCount): |
471 |
0 |
child = header.child(i) |
472 |
0 |
text = self._script.getText(child, 0, -1) |
473 |
0 |
if text: |
474 |
0 |
utterances.append(text) |
475 |
0 |
elif header.text: |
476 |
0 |
text = self._script.getText(header, 0, -1) |
477 |
0 |
if text: |
478 |
0 |
utterances.append(text) |
479 |
|
|
480 |
0 |
if parent.__dict__.has_key("lastRow") and \ |
481 |
|
parent.lastRow != parent.table.getRowAtIndex(obj.index): |
482 |
0 |
if dynamicRowHeaders.has_key(table): |
483 |
0 |
column = dynamicRowHeaders[table] |
484 |
0 |
header = getDynamicColumnHeaderCell(obj, column) |
485 |
0 |
if header.childCount > 0: |
486 |
0 |
for i in range(0, header.childCount): |
487 |
0 |
child = header.child(i) |
488 |
0 |
text = self._script.getText(child, 0, -1) |
489 |
0 |
if text: |
490 |
0 |
utterances.append(text) |
491 |
0 |
elif header.text: |
492 |
0 |
text = self._script.getText(header, 0, -1) |
493 |
0 |
if text: |
494 |
0 |
utterances.append(text) |
495 |
|
|
496 |
0 |
if isSpreadSheetCell(obj): |
497 |
0 |
if not already_focused: |
498 |
0 |
if settings.readTableCellRow: |
499 |
0 |
parent = obj.parent |
500 |
0 |
row = parent.table.getRowAtIndex(obj.index) |
501 |
0 |
column = parent.table.getColumnAtIndex(obj.index) |
502 |
|
|
503 |
|
# This is an indication of whether we should speak all the |
504 |
|
# table cells (the user has moved focus up or down a row), |
505 |
|
# or just the current one (focus has moved left or right in |
506 |
|
# the same row). |
507 |
|
# |
508 |
0 |
speakAll = True |
509 |
0 |
if parent.__dict__.has_key("lastRow") and \ |
510 |
|
parent.__dict__.has_key("lastColumn"): |
511 |
0 |
speakAll = (parent.lastRow != row) or \ |
512 |
|
((row == 0 or row == parent.table.nRows-1) and \ |
513 |
|
parent.lastColumn == column) |
514 |
|
|
515 |
0 |
if speakAll: |
516 |
0 |
for i in range(0, parent.table.nColumns): |
517 |
0 |
accRow = parent.table.getAccessibleAt(row, i) |
518 |
0 |
cell = atspi.Accessible.makeAccessible(accRow) |
519 |
0 |
showing = cell.state.count( \ |
520 |
|
atspi.Accessibility.STATE_SHOWING) |
521 |
0 |
if showing: |
522 |
0 |
utterances.extend(self._getSpeechForTableCell(\ |
523 |
|
cell, already_focused)) |
524 |
|
else: |
525 |
0 |
utterances.extend(self._getSpeechForTableCell(obj, |
526 |
0 |
already_focused)) |
527 |
|
else: |
528 |
0 |
utterances.extend(self._getSpeechForTableCell(obj, |
529 |
0 |
already_focused)) |
530 |
|
else: |
531 |
0 |
speechGen = speechgenerator.SpeechGenerator |
532 |
0 |
utterances.extend(speechGen._getSpeechForTableCellRow(self, obj, |
533 |
0 |
already_focused)) |
534 |
|
|
535 |
0 |
return utterances |
536 |
|
|
537 |
1 |
def _getSpeechForTableCell(self, obj, already_focused): |
538 |
|
"""Get the speech for a table cell. If this isn't inside a |
539 |
|
spread sheet, just return the utterances returned by the default |
540 |
|
table cell speech handler. |
541 |
|
|
542 |
|
Arguments: |
543 |
|
- obj: the table cell |
544 |
|
- already_focused: False if object just received focus |
545 |
|
|
546 |
|
Returns a list of utterances to be spoken for the object. |
547 |
|
""" |
548 |
|
|
549 |
0 |
global inputLineForCell, isSpreadSheetCell, locateInputLine |
550 |
|
|
551 |
0 |
if isSpreadSheetCell(obj): |
552 |
0 |
utterances = [] |
553 |
|
|
554 |
0 |
if inputLineForCell == None: |
555 |
0 |
inputLineForCell = locateInputLine(obj) |
556 |
|
|
557 |
0 |
if obj.text: |
558 |
0 |
objectText = self._script.getText(obj, 0, -1) |
559 |
0 |
utterances.append(objectText) |
560 |
|
|
561 |
0 |
nameList = obj.name.split() |
562 |
0 |
utterances.append(nameList[1]) |
563 |
|
else: |
564 |
|
# Check to see how many children this table cell has. If it's |
565 |
|
# just one (or none), then pass it on to the superclass to be |
566 |
|
# processed. |
567 |
|
# |
568 |
|
# If it's more than one, then get the speech for each child, |
569 |
|
# and call this method again. |
570 |
|
# |
571 |
0 |
if obj.childCount <= 1: |
572 |
0 |
speechGen = speechgenerator.SpeechGenerator |
573 |
0 |
utterances = speechGen._getSpeechForTableCell(self, obj, |
574 |
0 |
already_focused) |
575 |
|
else: |
576 |
0 |
utterances = [] |
577 |
0 |
for i in range(0, obj.childCount): |
578 |
0 |
child = obj.child(i) |
579 |
0 |
utterances.extend(self._getSpeechForTableCell(child, |
580 |
0 |
already_focused)) |
581 |
|
|
582 |
0 |
return utterances |
583 |
|
|
584 |
1 |
def _getSpeechForToggleButton(self, obj, already_focused): |
585 |
|
"""Get the speech for a toggle button. We always want to speak the |
586 |
|
state if it's on a toolbar. |
587 |
|
|
588 |
|
Arguments: |
589 |
|
- obj: the toggle button |
590 |
|
- already_focused: False if object just received focus |
591 |
|
|
592 |
|
Returns a list of utterances to be spoken for the object. |
593 |
|
""" |
594 |
|
|
595 |
4 |
utterances = [] |
596 |
4 |
if obj.parent.role == rolenames.ROLE_TOOL_BAR: |
597 |
0 |
if obj.state.count(atspi.Accessibility.STATE_CHECKED): |
598 |
0 |
checkedState = _("on") |
599 |
|
else: |
600 |
0 |
checkedState = _("off") |
601 |
|
|
602 |
0 |
utterances.append(obj.name) |
603 |
0 |
utterances.append(checkedState) |
604 |
|
else: |
605 |
4 |
speechGen = speechgenerator.SpeechGenerator |
606 |
4 |
utterances.extend(speechGen._getSpeechForToggleButton(self, obj, |
607 |
4 |
already_focused)) |
608 |
|
|
609 |
4 |
return utterances |
610 |
|
|
611 |
1 |
def _getSpeechForPushButton(self, obj, already_focused): |
612 |
|
"""Get the speech for a push button. We always want to speak the |
613 |
|
state if it's on a toolbar. |
614 |
|
|
615 |
|
Arguments: |
616 |
|
- obj: the push button |
617 |
|
- already_focused: False if object just received focus |
618 |
|
|
619 |
|
Returns a list of utterances to be spoken for the object. |
620 |
|
""" |
621 |
|
|
622 |
8 |
utterances = [] |
623 |
8 |
if obj.parent.role == rolenames.ROLE_TOOL_BAR: |
624 |
0 |
if obj.state.count(atspi.Accessibility.STATE_CHECKED): |
625 |
0 |
checkedState = _("on") |
626 |
|
else: |
627 |
0 |
checkedState = _("off") |
628 |
|
|
629 |
0 |
utterances.append(obj.name) |
630 |
0 |
utterances.append(checkedState) |
631 |
|
else: |
632 |
8 |
speechGen = speechgenerator.SpeechGenerator |
633 |
8 |
utterances.extend(speechGen._getSpeechForPushButton(self, obj, |
634 |
8 |
already_focused)) |
635 |
|
|
636 |
8 |
return utterances |
637 |
|
|
638 |
|
######################################################################## |
639 |
|
# # |
640 |
|
# The StarOffice script class. # |
641 |
|
# # |
642 |
|
######################################################################## |
643 |
|
|
644 |
2 |
class Script(default.Script): |
645 |
|
|
646 |
1 |
def __init__(self, app): |
647 |
|
"""Creates a new script for the given application. |
648 |
|
|
649 |
|
Arguments: |
650 |
|
- app: the application to create a script for. |
651 |
|
""" |
652 |
|
|
653 |
4 |
default.Script.__init__(self, app) |
654 |
|
|
655 |
|
# Set the debug level for all the methods in this script. |
656 |
|
# |
657 |
4 |
self.debugLevel = debug.LEVEL_FINEST |
658 |
|
|
659 |
|
# A handle to the last spread sheet cell encountered. |
660 |
|
# |
661 |
4 |
self.lastCell = None |
662 |
|
|
663 |
|
# The following variables will be used to try to determine if we've |
664 |
|
# already handled this misspelt word (see readMisspeltWord() for |
665 |
|
# more details. |
666 |
|
|
667 |
4 |
self.lastTextLength = -1 |
668 |
4 |
self.lastBadWord = '' |
669 |
4 |
self.lastStartOff = -1 |
670 |
4 |
self.lastEndOff = -1 |
671 |
|
|
672 |
|
# Used to determine if the user has double-clicked the dynamic |
673 |
|
# row/column hotkeys. |
674 |
|
|
675 |
4 |
self.lastDynamicEvent = None |
676 |
|
|
677 |
|
# Used to determine whether the caret has moved to a new paragraph. |
678 |
|
# |
679 |
4 |
self.currentParagraph = None |
680 |
|
|
681 |
|
# Set the number of retries after a COMM_FAILURE to 1. The default |
682 |
|
# of 5 was not allowing Orca to be responsive in the event of OOo |
683 |
|
# going into crash recovery mode (see bug #397787). |
684 |
|
# |
685 |
4 |
self.commFailureAttemptLimit = 1 |
686 |
|
|
687 |
|
# The default set of text attributes to speak to the user. The |
688 |
|
# only difference over the default set in settings.py is to add |
689 |
|
# in "left-margin:" and "right-margin:". |
690 |
|
|
691 |
4 |
settings.enabledTextAttributes = "size:; family-name:; weight:400; indent:0; left-margin:0; right-margin:0; underline:none; strikethrough:false; justification:left; style:normal;" |
692 |
|
|
693 |
|
# [[[TODO: JD - HACK because we won't get events from toggle |
694 |
|
# buttons on the Formatting toolbar until we "tickle/poke" |
695 |
|
# the hierarchy. But we only want to do it once. |
696 |
|
# See bug #363830 and OOo issue #70872.]]] |
697 |
|
# |
698 |
4 |
self.tickled = None |
699 |
|
|
700 |
1 |
def getBrailleGenerator(self): |
701 |
|
"""Returns the braille generator for this script. |
702 |
|
""" |
703 |
|
|
704 |
4 |
return BrailleGenerator(self) |
705 |
|
|
706 |
1 |
def getSpeechGenerator(self): |
707 |
|
"""Returns the speech generator for this script. |
708 |
|
""" |
709 |
|
|
710 |
4 |
return SpeechGenerator(self) |
711 |
|
|
712 |
1 |
def setupInputEventHandlers(self): |
713 |
|
"""Defines InputEventHandler fields for this script that can be |
714 |
|
called by the key and braille bindings. In this particular case, |
715 |
|
we just want to be able to add a handler to return the contents of |
716 |
|
the input line. |
717 |
|
""" |
718 |
|
|
719 |
4 |
default.Script.setupInputEventHandlers(self) |
720 |
|
|
721 |
4 |
self.inputEventHandlers["speakInputLineHandler"] = \ |
722 |
|
input_event.InputEventHandler( |
723 |
|
Script.speakInputLine, |
724 |
4 |
_("Speaks the contents of the input line.")) |
725 |
|
|
726 |
4 |
self.inputEventHandlers["setDynamicColumnHeadersHandler"] = \ |
727 |
|
input_event.InputEventHandler( |
728 |
|
Script.setDynamicColumnHeaders, |
729 |
4 |
_("Set the row to use as dynamic column headers when speaking calc cells.")) |
730 |
|
|
731 |
4 |
self.inputEventHandlers["setDynamicRowHeadersHandler"] = \ |
732 |
|
input_event.InputEventHandler( |
733 |
|
Script.setDynamicRowHeaders, |
734 |
4 |
_("Set the column to use as dynamic row headers to use when speaking calc cells.")) |
735 |
|
|
736 |
1 |
def getKeyBindings(self): |
737 |
|
"""Defines the key bindings for this script. Setup the default |
738 |
|
key bindings, then add one in for reading the input line. |
739 |
|
|
740 |
|
Returns an instance of keybindings.KeyBindings. |
741 |
|
""" |
742 |
|
|
743 |
4 |
keyBindings = default.Script.getKeyBindings(self) |
744 |
|
|
745 |
4 |
keyBindings.add( |
746 |
|
keybindings.KeyBinding( |
747 |
|
"a", |
748 |
4 |
1 << settings.MODIFIER_ORCA, |
749 |
4 |
1 << settings.MODIFIER_ORCA, |
750 |
4 |
self.inputEventHandlers["speakInputLineHandler"])) |
751 |
|
|
752 |
4 |
keyBindings.add( |
753 |
|
keybindings.KeyBinding( |
754 |
|
"r", |
755 |
4 |
1 << settings.MODIFIER_ORCA, |
756 |
4 |
1 << settings.MODIFIER_ORCA, |
757 |
4 |
self.inputEventHandlers["setDynamicColumnHeadersHandler"])) |
758 |
|
|
759 |
4 |
keyBindings.add( |
760 |
|
keybindings.KeyBinding( |
761 |
|
"c", |
762 |
4 |
1 << settings.MODIFIER_ORCA, |
763 |
4 |
1 << settings.MODIFIER_ORCA, |
764 |
4 |
self.inputEventHandlers["setDynamicRowHeadersHandler"])) |
765 |
|
|
766 |
4 |
return keyBindings |
767 |
|
|
768 |
1 |
def checkForTableBoundry(self, oldFocus, newFocus): |
769 |
|
"""Check to see if we've entered or left a table. |
770 |
|
When entering a table, announce the table dimensions. |
771 |
|
When leaving a table, announce that the table has been exited. |
772 |
|
|
773 |
|
Arguments: |
774 |
|
- oldFocus: Accessible that is the old locus of focus |
775 |
|
- newFocus: Accessible that is the new locus of focus |
776 |
|
""" |
777 |
|
|
778 |
47 |
if oldFocus == None or newFocus == None: |
779 |
0 |
return |
780 |
|
|
781 |
47 |
oldFocusIsTable = None |
782 |
285 |
while oldFocus.role != rolenames.ROLE_APPLICATION: |
783 |
238 |
if oldFocus.role == rolenames.ROLE_TABLE: |
784 |
0 |
oldFocusIsTable = oldFocus |
785 |
0 |
break |
786 |
238 |
oldFocus = oldFocus.parent |
787 |
|
|
788 |
47 |
newFocusIsTable = None |
789 |
306 |
while newFocus.role != rolenames.ROLE_APPLICATION: |
790 |
259 |
if newFocus.role == rolenames.ROLE_TABLE: |
791 |
0 |
newFocusIsTable = newFocus |
792 |
0 |
break |
793 |
259 |
newFocus = newFocus.parent |
794 |
|
|
795 |
47 |
if oldFocusIsTable == None and newFocusIsTable != None: |
796 |
0 |
rows = newFocusIsTable.table.nRows |
797 |
0 |
columns = newFocusIsTable.table.nColumns |
798 |
0 |
line = _("table with %d rows and %d columns.") % (rows, columns) |
799 |
0 |
speech.speak(line) |
800 |
47 |
elif oldFocusIsTable != None and newFocusIsTable == None: |
801 |
0 |
speech.speak(_("leaving table.")) |
802 |
|
|
803 |
1 |
def speakInputLine(self, inputEvent): |
804 |
|
"""Speak the contents of the spread sheet input line (assuming we |
805 |
|
have a handle to it - generated when we first focus on a spread |
806 |
|
sheet table cell. |
807 |
|
|
808 |
|
This will be either the contents of the table cell that has focus |
809 |
|
or the formula associated with it. |
810 |
|
|
811 |
|
Arguments: |
812 |
|
- inputEvent: if not None, the input event that caused this action. |
813 |
|
""" |
814 |
|
|
815 |
0 |
debug.println(self.debugLevel, "StarOffice.speakInputLine.") |
816 |
|
|
817 |
|
# Check to see if the current focus is a table cell. |
818 |
|
# |
819 |
0 |
if isSpreadSheetCell(orca_state.locusOfFocus): |
820 |
0 |
if inputLineForCell and inputLineForCell.text: |
821 |
0 |
inputLine = self.getText(inputLineForCell, 0, -1) |
822 |
0 |
if not inputLine: |
823 |
0 |
inputLine = _("empty") |
824 |
0 |
debug.println(self.debugLevel, |
825 |
0 |
"StarOffice.speakInputLine: contents: %s" % inputLine) |
826 |
0 |
speech.speak(inputLine) |
827 |
|
|
828 |
1 |
def getTableRow(self, cell): |
829 |
|
"""Get the row number in the table that this table cell is on. |
830 |
|
|
831 |
|
Arguments: |
832 |
|
- cell: the table cell to get the row number for. |
833 |
|
|
834 |
|
Return the row number that this table cell is on, or None if |
835 |
|
this isn't a table cell. |
836 |
|
""" |
837 |
|
|
838 |
0 |
row = None |
839 |
0 |
cell = adjustForWriterTable(cell) |
840 |
0 |
if cell.role == rolenames.ROLE_TABLE_CELL: |
841 |
0 |
parent = cell.parent |
842 |
0 |
if parent and parent.table: |
843 |
0 |
row = parent.table.getRowAtIndex(cell.index) |
844 |
|
|
845 |
0 |
return row |
846 |
|
|
847 |
1 |
def getTableColumn(self, cell): |
848 |
|
"""Get the column number in the table that this table cell is on. |
849 |
|
|
850 |
|
Arguments: |
851 |
|
- cell: the table cell to get the column number for. |
852 |
|
|
853 |
|
Return the column number that this table cell is on, or None if |
854 |
|
this isn't a table cell. |
855 |
|
""" |
856 |
|
|
857 |
0 |
column = None |
858 |
0 |
cell = adjustForWriterTable(cell) |
859 |
0 |
if cell.role == rolenames.ROLE_TABLE_CELL: |
860 |
0 |
parent = cell.parent |
861 |
0 |
if parent and parent.table: |
862 |
0 |
column = parent.table.getColumnAtIndex(cell.index) |
863 |
|
|
864 |
0 |
return column |
865 |
|
|
866 |
1 |
def setDynamicColumnHeaders(self, inputEvent): |
867 |
|
"""Set the row for the dynamic header columns to use when speaking |
868 |
|
calc cell entries. In order to set the row, the user should first set |
869 |
|
focus to the row that they wish to define and then press Insert-r. |
870 |
|
|
871 |
|
Once the user has defined the row, it will be used to first speak |
872 |
|
this header when moving between columns. |
873 |
|
|
874 |
|
A "double-click" of the Insert-c hotkey, will clear the dynamic |
875 |
|
header column. |
876 |
|
|
877 |
|
Arguments: |
878 |
|
- inputEvent: if not None, the input event that caused this action. |
879 |
|
""" |
880 |
|
|
881 |
0 |
global dynamicColumnHeaders, getTable |
882 |
|
|
883 |
0 |
debug.println(self.debugLevel, "StarOffice.setDynamicColumnHeaders.") |
884 |
|
|
885 |
0 |
clickCount = self.getClickCount(self.lastDynamicEvent, inputEvent) |
886 |
0 |
table = getTable(orca_state.locusOfFocus) |
887 |
0 |
if table: |
888 |
0 |
row = self.getTableRow(orca_state.locusOfFocus) |
889 |
0 |
if clickCount == 2: |
890 |
0 |
try: |
891 |
0 |
del dynamicColumnHeaders[table] |
892 |
0 |
line = _("Dynamic column header cleared.") |
893 |
0 |
speech.speak(line) |
894 |
0 |
braille.displayMessage(line) |
895 |
0 |
except: |
896 |
0 |
pass |
897 |
|
else: |
898 |
0 |
dynamicColumnHeaders[table] = row |
899 |
0 |
line = _("Dynamic column header set for row ") + str(row+1) |
900 |
0 |
speech.speak(line) |
901 |
0 |
braille.displayMessage(line) |
902 |
0 |
self.lastDynamicEvent = inputEvent |
903 |
|
|
904 |
0 |
return True |
905 |
|
|
906 |
1 |
def columnConvert(self, column): |
907 |
|
""" Convert a spreadsheet column into it's column label |
908 |
|
|
909 |
|
Arguments: |
910 |
|
- column: the column number to convert. |
911 |
|
|
912 |
|
Returns a string representing the spread sheet column. |
913 |
|
""" |
914 |
|
|
915 |
0 |
BASE26="ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
916 |
|
|
917 |
0 |
if column <= len(BASE26): |
918 |
0 |
return BASE26[column-1] |
919 |
|
|
920 |
0 |
res = "" |
921 |
0 |
while column > 0: |
922 |
0 |
digit = column % len(BASE26) |
923 |
0 |
res = " " + BASE26[digit-1] + res |
924 |
0 |
column /= len(BASE26) |
925 |
|
|
926 |
0 |
return res |
927 |
|
|
928 |
1 |
def setDynamicRowHeaders(self, inputEvent): |
929 |
|
"""Set the column for the dynamic header rows to use when speaking |
930 |
|
calc cell entries. In order to set the column, the user should first |
931 |
|
set focus to the column that they wish to define and then press |
932 |
|
Insert-c. |
933 |
|
|
934 |
|
Once the user has defined the column, it will be used to first speak |
935 |
|
this header when moving between rows. |
936 |
|
|
937 |
|
A "double-click" of the Insert-r hotkey, will clear the dynamic |
938 |
|
header row. |
939 |
|
|
940 |
|
Arguments: |
941 |
|
- inputEvent: if not None, the input event that caused this action. |
942 |
|
""" |
943 |
|
|
944 |
0 |
global dynamicRowHeaders, getTable |
945 |
|
|
946 |
0 |
debug.println(self.debugLevel, "StarOffice.setDynamicRowHeaders.") |
947 |
|
|
948 |
0 |
clickCount = self.getClickCount(self.lastDynamicEvent, inputEvent) |
949 |
0 |
table = getTable(orca_state.locusOfFocus) |
950 |
0 |
if table: |
951 |
0 |
column = self.getTableColumn(orca_state.locusOfFocus) |
952 |
0 |
if clickCount == 2: |
953 |
0 |
try: |
954 |
0 |
del dynamicRowHeaders[table] |
955 |
0 |
line = _("Dynamic row header cleared.") |
956 |
0 |
speech.speak(line) |
957 |
0 |
braille.displayMessage(line) |
958 |
0 |
except: |
959 |
0 |
pass |
960 |
|
else: |
961 |
0 |
dynamicRowHeaders[table] = column |
962 |
0 |
line = _("Dynamic row header set for column %s") % \ |
963 |
|
self.columnConvert(column+1) |
964 |
0 |
speech.speak(line) |
965 |
0 |
braille.displayMessage(line) |
966 |
|
|
967 |
0 |
self.lastDynamicEvent = inputEvent |
968 |
|
|
969 |
0 |
return True |
970 |
|
|
971 |
1 |
def readMisspeltWord(self, event, pane): |
972 |
|
"""Speak/braille the current misspelt word plus its context. |
973 |
|
The spell check dialog contains a "paragraph" which shows the |
974 |
|
context for the current spelling mistake. After speaking/brailling |
975 |
|
the default action for this component, that a selection of the |
976 |
|
surronding text from that paragraph with the misspelt word is also |
977 |
|
spoken. |
978 |
|
|
979 |
|
Arguments: |
980 |
|
- event: the event. |
981 |
|
- pane: the option pane in the spell check dialog. |
982 |
|
""" |
983 |
|
|
984 |
0 |
paragraph = self.findByRole(pane, rolenames.ROLE_PARAGRAPH) |
985 |
|
|
986 |
|
# Determine which word is the misspelt word. This word will have |
987 |
|
# non-default text attributes associated with it. |
988 |
|
|
989 |
0 |
textLength = paragraph[0].text.characterCount |
990 |
0 |
startFound = False |
991 |
0 |
startOff = 0 |
992 |
0 |
endOff = textLength |
993 |
0 |
for i in range(0, textLength): |
994 |
0 |
attributes = paragraph[0].text.getAttributes(i) |
995 |
0 |
if len(attributes[0]) != 0: |
996 |
0 |
if not startFound: |
997 |
0 |
startOff = i |
998 |
0 |
startFound = True |
999 |
|
else: |
1000 |
0 |
if startFound: |
1001 |
0 |
endOff = i |
1002 |
0 |
break |
1003 |
|
|
1004 |
0 |
badWord = self.getText(paragraph[0], startOff, endOff - 1) |
1005 |
|
|
1006 |
|
# Note that we often get two or more of these focus or property-change |
1007 |
|
# events each time there is a new misspelt word. We extract the |
1008 |
|
# length of the line of text, the misspelt word, the start and end |
1009 |
|
# offsets for that word and compare them against the values saved |
1010 |
|
# from the last time this routine was called. If they are the same |
1011 |
|
# then we ignore it. |
1012 |
|
|
1013 |
0 |
debug.println(self.debugLevel, \ |
1014 |
0 |
"StarOffice.readMisspeltWord: type=%s word=%s(%d,%d) len=%d" % \ |
1015 |
|
(event.type, badWord, startOff, endOff, textLength)) |
1016 |
|
|
1017 |
0 |
if (textLength == self.lastTextLength) and \ |
1018 |
|
(badWord == self.lastBadWord) and \ |
1019 |
|
(startOff == self.lastStartOff) and \ |
1020 |
|
(endOff == self.lastEndOff): |
1021 |
0 |
return |
1022 |
|
|
1023 |
|
# Create a list of all the words found in the misspelt paragraph. |
1024 |
|
# |
1025 |
0 |
text = self.getText(paragraph[0], 0, -1) |
1026 |
0 |
allTokens = text.split() |
1027 |
|
|
1028 |
0 |
self.speakMisspeltWord(allTokens, badWord) |
1029 |
|
|
1030 |
|
# Save misspelt word information for comparison purposes next |
1031 |
|
# time around. |
1032 |
|
# |
1033 |
0 |
self.lastTextLength = textLength |
1034 |
0 |
self.lastBadWord = badWord |
1035 |
0 |
self.lastStartOff = startOff |
1036 |
0 |
self.lastEndOff = endOff |
1037 |
|
|
1038 |
1 |
def endOfLink(self, obj, word, startOffset, endOffset): |
1039 |
|
"""Return an indication of whether the given word contains the |
1040 |
|
end of a hypertext link. |
1041 |
|
|
1042 |
|
Arguments: |
1043 |
|
- obj: an Accessible object that implements the AccessibleText |
1044 |
|
interface |
1045 |
|
- word: the word to check |
1046 |
|
- startOffset: the start offset for this word |
1047 |
|
- endOffset: the end offset for this word |
1048 |
|
|
1049 |
|
Returns True if this word contains the end of a hypertext link. |
1050 |
|
""" |
1051 |
|
|
1052 |
0 |
nLinks = obj.hypertext.getNLinks() |
1053 |
0 |
links = [] |
1054 |
0 |
for i in range(0, nLinks): |
1055 |
0 |
links.append(obj.hypertext.getLink(i)) |
1056 |
|
|
1057 |
0 |
for i in range(0, len(links)): |
1058 |
0 |
if links[i].endIndex > startOffset and \ |
1059 |
|
links[i].endIndex <= endOffset: |
1060 |
0 |
return True |
1061 |
|
|
1062 |
0 |
return False |
1063 |
|
|
1064 |
1 |
def sayWriterWord(self, obj, word, startOffset, endOffset): |
1065 |
|
"""Speaks the given word in the appropriate voice. If this word is |
1066 |
|
a hypertext link and it is also at the end offset for one of the |
1067 |
|
links, then the word "link" is also spoken. |
1068 |
|
|
1069 |
|
Arguments: |
1070 |
|
- obj: an Accessible object that implements the AccessibleText |
1071 |
|
interface |
1072 |
|
- word: the word to speak |
1073 |
|
- startOffset: the start offset for this word |
1074 |
|
- endOffset: the end offset for this word |
1075 |
|
""" |
1076 |
|
|
1077 |
0 |
voices = settings.voices |
1078 |
|
|
1079 |
0 |
for i in range(startOffset, endOffset): |
1080 |
0 |
if self.getLinkIndex(obj, i) >= 0: |
1081 |
0 |
voice = voices[settings.HYPERLINK_VOICE] |
1082 |
0 |
break |
1083 |
0 |
elif word.isupper(): |
1084 |
0 |
voice = voices[settings.UPPERCASE_VOICE] |
1085 |
|
else: |
1086 |
0 |
voice = voices[settings.DEFAULT_VOICE] |
1087 |
|
|
1088 |
0 |
speech.speak(word, voice) |
1089 |
0 |
if self.endOfLink(obj, word, startOffset, endOffset): |
1090 |
0 |
speech.speak(_("link")) |
1091 |
|
|
1092 |
1 |
def isSetupDialog(self, obj): |
1093 |
|
""" Check to see if this object is in the Setup dialog by walking |
1094 |
|
back up the object hierarchy until we get to the dialog object and |
1095 |
|
checking to see if it has a name that starts with "Welcome to |
1096 |
|
StarOffice". |
1097 |
|
|
1098 |
|
Arguments: |
1099 |
|
- obj: an Accessible object that implements the AccessibleText |
1100 |
|
interface |
1101 |
|
|
1102 |
|
Returns an indication of whether this object is in the Setup dialog. |
1103 |
|
""" |
1104 |
|
|
1105 |
138 |
found = False |
1106 |
846 |
while obj and obj.role != rolenames.ROLE_APPLICATION: |
1107 |
708 |
if obj.role == rolenames.ROLE_DIALOG and \ |
1108 |
|
(obj.name and obj.name.startswith(_("Welcome to StarOffice"))): |
1109 |
0 |
debug.println(self.debugLevel, |
1110 |
0 |
"StarOffice.isSetupDialog: True.") |
1111 |
0 |
found = True |
1112 |
|
|
1113 |
708 |
obj = obj.parent |
1114 |
|
|
1115 |
138 |
return found |
1116 |
|
|
1117 |
1 |
def speakSetupLabel(self, label): |
1118 |
|
"""Speak this Setup dialog label. |
1119 |
|
|
1120 |
|
Arguments: |
1121 |
|
- label: the Setup dialog Label. |
1122 |
|
""" |
1123 |
|
|
1124 |
0 |
text = self.getDisplayedText(label) |
1125 |
0 |
if text: |
1126 |
0 |
speech.speak(text) |
1127 |
|
|
1128 |
1 |
def handleSetupPanel(self, panel): |
1129 |
|
"""Find all the labels in this Setup panel and speak them. |
1130 |
|
|
1131 |
|
Arguments: |
1132 |
|
- panel: the Setup panel. |
1133 |
|
""" |
1134 |
|
|
1135 |
0 |
allLabels = self.findByRole(panel, rolenames.ROLE_LABEL) |
1136 |
0 |
for i in range(0, len(allLabels)): |
1137 |
0 |
self.speakSetupLabel(allLabels[i]) |
1138 |
|
|
1139 |
|
# This method tries to detect and handle the following cases: |
1140 |
|
# 0) Writer: find command. |
1141 |
|
# 1) Writer: text paragraph. |
1142 |
|
# 2) Writer: spell checking dialog. |
1143 |
|
# 3) Welcome to StarOffice dialog. |
1144 |
|
# 4) Calc: cell editor. |
1145 |
|
# 5) Calc: name box. |
1146 |
|
|
1147 |
1 |
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus): |
1148 |
|
"""Called when the visual object with focus changes. |
1149 |
|
|
1150 |
|
Arguments: |
1151 |
|
- event: if not None, the Event that caused the change |
1152 |
|
- oldLocusOfFocus: Accessible that is the old locus of focus |
1153 |
|
- newLocusOfFocus: Accessible that is the new locus of focus |
1154 |
|
""" |
1155 |
|
|
1156 |
133 |
brailleGen = self.brailleGenerator |
1157 |
133 |
speechGen = self.speechGenerator |
1158 |
|
|
1159 |
133 |
debug.printObjectEvent(self.debugLevel, |
1160 |
133 |
event, |
1161 |
133 |
event.source.toString()) |
1162 |
|
|
1163 |
|
# self.printAncestry(event.source) |
1164 |
|
|
1165 |
|
# 0) Writer: find command |
1166 |
|
# |
1167 |
|
# Check to see if this is this is for the find command. See |
1168 |
|
# comment #18 of bug #354463. |
1169 |
|
# |
1170 |
133 |
if self.findCommandRun and \ |
1171 |
|
event.type == "object:state-changed:focused": |
1172 |
0 |
self.findCommandRun = False |
1173 |
0 |
self.find() |
1174 |
0 |
return |
1175 |
|
|
1176 |
|
# 1) Writer: text paragraph. |
1177 |
|
# |
1178 |
|
# We need to handle two things here: |
1179 |
|
# |
1180 |
|
# If the old locus of focus was on the File->New->Text Document |
1181 |
|
# menu item and we are currently have focus on an empty text |
1182 |
|
# paragraph, then we've just created the first new text document |
1183 |
|
# in Writer. Announce it by doing a "where am I". |
1184 |
|
# |
1185 |
|
# Also, when the focus is on a paragraph in the Document view of |
1186 |
|
# the Writer, then just speak/braille the current line (rather than |
1187 |
|
# speaking a bogus initial "paragraph" utterance as well). |
1188 |
|
|
1189 |
133 |
rolesList = [rolenames.ROLE_PARAGRAPH, \ |
1190 |
|
rolenames.ROLE_UNKNOWN, \ |
1191 |
|
rolenames.ROLE_SCROLL_PANE, \ |
1192 |
|
rolenames.ROLE_PANEL, \ |
1193 |
|
rolenames.ROLE_ROOT_PANE, \ |
1194 |
|
rolenames.ROLE_FRAME] |
1195 |
133 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1196 |
2 |
debug.println(self.debugLevel, |
1197 |
2 |
"StarOffice.locusOfFocusChanged - Writer: text paragraph.") |
1198 |
|
|
1199 |
2 |
result = self.getTextLineAtCaret(event.source) |
1200 |
2 |
result[0] = result[0].decode("UTF-8") |
1201 |
|
|
1202 |
2 |
if oldLocusOfFocus.role == rolenames.ROLE_MENU_ITEM and \ |
1203 |
|
oldLocusOfFocus.name == _("Text Document") and \ |
1204 |
|
len(result[0]) == 0: |
1205 |
0 |
self.whereAmI(None) |
1206 |
|
|
1207 |
|
# Check to see if there are any hypertext links in this paragraph. |
1208 |
|
# If no, then just speak the whole line. Otherwise, split the |
1209 |
|
# line into words and call sayWriterWord() to speak that token |
1210 |
|
# in the appropriate voice. |
1211 |
|
# |
1212 |
2 |
hypertext = event.source.hypertext |
1213 |
2 |
if not hypertext or (hypertext.getNLinks() == 0): |
1214 |
2 |
if settings.enableSpeechIndentation: |
1215 |
0 |
self.speakTextIndentation(event.source, |
1216 |
0 |
result[0].encode("UTF-8")) |
1217 |
2 |
speech.speak(result[0].encode("UTF-8"), None, False) |
1218 |
|
else: |
1219 |
0 |
started = False |
1220 |
0 |
startOffset = 0 |
1221 |
0 |
for i in range(0, len(result[0])): |
1222 |
0 |
if result[0][i] == ' ': |
1223 |
0 |
if started: |
1224 |
0 |
endOffset = i |
1225 |
0 |
self.sayWriterWord(event.source, |
1226 |
0 |
result[0][startOffset:endOffset+1].encode("UTF-8"), |
1227 |
0 |
startOffset, endOffset) |
1228 |
0 |
startOffset = i |
1229 |
0 |
started = False |
1230 |
|
else: |
1231 |
0 |
if not started: |
1232 |
0 |
startOffset = i |
1233 |
0 |
started = True |
1234 |
|
|
1235 |
0 |
if started: |
1236 |
0 |
endOffset = len(result[0]) |
1237 |
0 |
self.sayWriterWord(event.source, |
1238 |
0 |
result[0][startOffset:endOffset].encode("UTF-8"), |
1239 |
0 |
startOffset, endOffset) |
1240 |
|
|
1241 |
2 |
braille.displayRegions(brailleGen.getBrailleRegions(event.source)) |
1242 |
|
|
1243 |
2 |
return |
1244 |
|
|
1245 |
|
# 2) Writer: spell checking dialog. |
1246 |
|
# |
1247 |
|
# Check to see if the Spell Check dialog has just appeared and got |
1248 |
|
# focus. If it has, then speak/braille the current misspelt word |
1249 |
|
# plus its context. |
1250 |
|
# |
1251 |
|
# Note that in order to make sure that this focus event is for the |
1252 |
|
# spell check dialog, a check is made of the localized name of the |
1253 |
|
# option pane. Translators for other locales will need to ensure that |
1254 |
|
# their translation of this string matches what StarOffice uses in |
1255 |
|
# that locale. |
1256 |
|
|
1257 |
131 |
rolesList = [rolenames.ROLE_PUSH_BUTTON, \ |
1258 |
|
rolenames.ROLE_OPTION_PANE, \ |
1259 |
|
rolenames.ROLE_DIALOG, \ |
1260 |
|
rolenames.ROLE_APPLICATION] |
1261 |
131 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1262 |
0 |
pane = event.source.parent |
1263 |
0 |
if pane.name.startswith(_("Spellcheck:")): |
1264 |
0 |
debug.println(self.debugLevel, |
1265 |
0 |
"StarOffice.locusOfFocusChanged - " \ |
1266 |
|
+ "Writer: spell check dialog.") |
1267 |
|
|
1268 |
0 |
self.readMisspeltWord(event, pane) |
1269 |
|
|
1270 |
|
# Fall-thru to process the event with the default handler. |
1271 |
|
|
1272 |
|
# 3) Welcome to StarOffice dialog. |
1273 |
|
# |
1274 |
|
# Check to see if the object that just got focus is in the Setup |
1275 |
|
# dialog. If it is, then check for a variety of scenerios. |
1276 |
|
|
1277 |
131 |
if self.isSetupDialog(event.source): |
1278 |
|
|
1279 |
|
# Check for 2. License Agreement: Scroll Down button. |
1280 |
|
# |
1281 |
0 |
rolesList = [rolenames.ROLE_PUSH_BUTTON, \ |
1282 |
|
rolenames.ROLE_PANEL, \ |
1283 |
|
rolenames.ROLE_OPTION_PANE, \ |
1284 |
|
rolenames.ROLE_DIALOG, \ |
1285 |
|
rolenames.ROLE_APPLICATION] |
1286 |
0 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1287 |
0 |
debug.println(self.debugLevel, |
1288 |
0 |
"StarOffice.locusOfFocusChanged - Setup dialog: " \ |
1289 |
|
+ "License Agreement screen: Scroll Down button.") |
1290 |
0 |
self.handleSetupPanel(event.source.parent) |
1291 |
0 |
speech.speak(_("Note that the Scroll Down button has to be pressed numerous times.")) |
1292 |
|
|
1293 |
|
# Check for 2. License Agreement: Accept button. |
1294 |
|
# |
1295 |
0 |
rolesList = [rolenames.ROLE_UNKNOWN, \ |
1296 |
|
rolenames.ROLE_SCROLL_PANE, \ |
1297 |
|
rolenames.ROLE_PANEL, \ |
1298 |
|
rolenames.ROLE_OPTION_PANE, \ |
1299 |
|
rolenames.ROLE_DIALOG, \ |
1300 |
|
rolenames.ROLE_APPLICATION] |
1301 |
0 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1302 |
0 |
debug.println(self.debugLevel, |
1303 |
0 |
"StarOffice.locusOfFocusChanged - Setup dialog: " \ |
1304 |
|
+ "License Agreement screen: accept button.") |
1305 |
0 |
speech.speak(_("License Agreement Accept button now has focus.")) |
1306 |
|
|
1307 |
|
# Check for 3. Personal Data: Transfer Personal Data check box. |
1308 |
|
# |
1309 |
0 |
rolesList = [rolenames.ROLE_CHECK_BOX, \ |
1310 |
|
rolenames.ROLE_PANEL, \ |
1311 |
|
rolenames.ROLE_OPTION_PANE, \ |
1312 |
|
rolenames.ROLE_DIALOG, \ |
1313 |
|
rolenames.ROLE_APPLICATION] |
1314 |
0 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1315 |
0 |
debug.println(self.debugLevel, |
1316 |
0 |
"StarOffice.locusOfFocusChanged - Setup dialog: " \ |
1317 |
|
+ "Personal Data: Transfer Personal Data check box.") |
1318 |
0 |
self.handleSetupPanel(event.source.parent) |
1319 |
|
|
1320 |
|
# Check for 4. User name: First Name text field. |
1321 |
|
# |
1322 |
0 |
rolesList = [rolenames.ROLE_TEXT, \ |
1323 |
|
rolenames.ROLE_PANEL, \ |
1324 |
|
rolenames.ROLE_OPTION_PANE, \ |
1325 |
|
rolenames.ROLE_DIALOG, \ |
1326 |
|
rolenames.ROLE_APPLICATION] |
1327 |
0 |
if self.isDesiredFocusedItem(event.source, rolesList) and \ |
1328 |
|
event.source.name == _("First name"): |
1329 |
0 |
debug.println(self.debugLevel, |
1330 |
0 |
"StarOffice.locusOfFocusChanged - Setup dialog: " \ |
1331 |
|
+ "User name: First Name text field.") |
1332 |
|
|
1333 |
|
# Just speak the informative labels at the top of the panel |
1334 |
|
# (and not the ones that have LABEL_FOR relationships). |
1335 |
|
# |
1336 |
0 |
panel = event.source.parent |
1337 |
0 |
allLabels = self.findByRole(panel, rolenames.ROLE_LABEL) |
1338 |
0 |
for i in range(0, len(allLabels)): |
1339 |
0 |
relations = allLabels[i].relations |
1340 |
0 |
hasLabelFor = False |
1341 |
0 |
for relation in relations: |
1342 |
0 |
if relation.getRelationType() \ |
1343 |
|
== atspi.Accessibility.RELATION_LABEL_FOR: |
1344 |
0 |
hasLabelFor = True |
1345 |
0 |
if not hasLabelFor: |
1346 |
0 |
self.speakSetupLabel(allLabels[i]) |
1347 |
|
|
1348 |
|
# Check for 5. Registration: Register Now radio button. |
1349 |
|
# |
1350 |
0 |
rolesList = [rolenames.ROLE_RADIO_BUTTON, \ |
1351 |
|
rolenames.ROLE_PANEL, \ |
1352 |
|
rolenames.ROLE_OPTION_PANE, \ |
1353 |
|
rolenames.ROLE_DIALOG, \ |
1354 |
|
rolenames.ROLE_APPLICATION] |
1355 |
0 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1356 |
0 |
debug.println(self.debugLevel, |
1357 |
0 |
"StarOffice.locusOfFocusChanged - Setup dialog: " \ |
1358 |
|
+ "Registration: Register Now radio button.") |
1359 |
0 |
self.handleSetupPanel(event.source.parent) |
1360 |
|
|
1361 |
|
# 4) Calc: cell editor. |
1362 |
|
# |
1363 |
|
# Check to see if we are editing a spread sheet cell. If so, just |
1364 |
|
# return to avoid uttering something like "Paragraph 0 paragraph". |
1365 |
|
# |
1366 |
131 |
rolesList = [rolenames.ROLE_PARAGRAPH, \ |
1367 |
|
rolenames.ROLE_PANEL, \ |
1368 |
|
rolenames.ROLE_UNKNOWN, \ |
1369 |
|
rolenames.ROLE_SCROLL_PANE, \ |
1370 |
|
rolenames.ROLE_PANEL, \ |
1371 |
|
rolenames.ROLE_ROOT_PANE, \ |
1372 |
|
rolenames.ROLE_FRAME, \ |
1373 |
|
rolenames.ROLE_APPLICATION] |
1374 |
131 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1375 |
0 |
debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \ |
1376 |
|
+ "Calc: cell editor.") |
1377 |
0 |
return |
1378 |
|
|
1379 |
|
# 5) Calc: name box |
1380 |
|
# |
1381 |
|
# Check to see if the focus has just moved to the Name Box combo |
1382 |
|
# box in Calc. If so, then replace the non-existent name with a |
1383 |
|
# simple one before falling through and calling the default |
1384 |
|
# locusOfFocusChanged method, which in turn will result in our |
1385 |
|
# _getSpeechForComboBox() method being called. |
1386 |
|
# |
1387 |
131 |
rolesList = [rolenames.ROLE_COMBO_BOX, \ |
1388 |
|
rolenames.ROLE_TOOL_BAR, \ |
1389 |
|
rolenames.ROLE_PANEL, \ |
1390 |
|
rolenames.ROLE_ROOT_PANE, \ |
1391 |
|
rolenames.ROLE_FRAME, \ |
1392 |
|
rolenames.ROLE_APPLICATION] |
1393 |
|
|
1394 |
131 |
if self.isDesiredFocusedItem(event.source, rolesList) \ |
1395 |
|
and (not event.source.name or len(event.source.name) == 0): |
1396 |
0 |
debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \ |
1397 |
|
+ "Calc: name box.") |
1398 |
|
|
1399 |
0 |
event.source.name = _("Move to cell") |
1400 |
|
|
1401 |
|
# Pass the event onto the parent class to be handled in the default way. |
1402 |
|
|
1403 |
131 |
default.Script.locusOfFocusChanged(self, event, |
1404 |
131 |
oldLocusOfFocus, newLocusOfFocus) |
1405 |
|
|
1406 |
|
# This method tries to detect and handle the following cases: |
1407 |
|
# 1) Setup dialog. |
1408 |
|
|
1409 |
1 |
def onWindowActivated(self, event): |
1410 |
|
"""Called whenever a property on an object changes. |
1411 |
|
|
1412 |
|
Arguments: |
1413 |
|
- event: the Event |
1414 |
|
""" |
1415 |
|
|
1416 |
7 |
debug.printObjectEvent(self.debugLevel, |
1417 |
7 |
event, |
1418 |
7 |
event.source.toString()) |
1419 |
|
|
1420 |
|
# self.printAncestry(event.source) |
1421 |
|
|
1422 |
|
# 1) Setup dialog. |
1423 |
|
# |
1424 |
|
# Check to see if the Setup dialog window has just been activated. |
1425 |
|
# If it has, then find the panel within it that has no name and |
1426 |
|
# speak all the labels within that panel. |
1427 |
|
# |
1428 |
7 |
if self.isSetupDialog(event.source): |
1429 |
0 |
debug.println(self.debugLevel, |
1430 |
0 |
"StarOffice.onWindowActivated - Setup dialog: Welcome screen.") |
1431 |
|
|
1432 |
0 |
allPanels = self.findByRole(event.source.parent, |
1433 |
0 |
rolenames.ROLE_PANEL) |
1434 |
0 |
for i in range(0, len(allPanels)): |
1435 |
0 |
if not allPanels[i].name: |
1436 |
0 |
allLabels = self.findByRole(allPanels[i], |
1437 |
0 |
rolenames.ROLE_LABEL) |
1438 |
0 |
for i in range(0, len(allLabels)): |
1439 |
0 |
self.speakSetupLabel(allLabels[i]) |
1440 |
|
else: |
1441 |
|
# Pass the event onto the parent class to be handled in the |
1442 |
|
# default way. |
1443 |
|
# |
1444 |
7 |
default.Script.onWindowActivated(self, event) |
1445 |
|
|
1446 |
|
|
1447 |
|
# This method tries to detect and handle the following cases: |
1448 |
|
# 1) Writer: spell checking dialog. |
1449 |
|
|
1450 |
1 |
def onNameChanged(self, event): |
1451 |
|
"""Called whenever a property on an object changes. |
1452 |
|
|
1453 |
|
Arguments: |
1454 |
|
- event: the Event |
1455 |
|
""" |
1456 |
|
|
1457 |
42 |
brailleGen = self.brailleGenerator |
1458 |
42 |
speechGen = self.speechGenerator |
1459 |
|
|
1460 |
42 |
debug.printObjectEvent(self.debugLevel, |
1461 |
42 |
event, |
1462 |
42 |
event.source.toString()) |
1463 |
|
|
1464 |
|
# self.printAncestry(event.source) |
1465 |
|
|
1466 |
|
# 1) Writer: spell checking dialog. |
1467 |
|
# |
1468 |
|
# Check to see if if we've had a property-change event for the |
1469 |
|
# accessible name for the option pane in the spell check dialog. |
1470 |
|
# This (hopefully) means that the user has just corrected a |
1471 |
|
# spelling mistake, in which case, speak/braille the current |
1472 |
|
# misspelt word plus its context. |
1473 |
|
# |
1474 |
|
# Note that in order to make sure that this focus event is for the |
1475 |
|
# spell check dialog, a check is made of the localized name of the |
1476 |
|
# option pane. Translators for other locales will need to ensure that |
1477 |
|
# their translation of this string matches what StarOffice uses in |
1478 |
|
# that locale. |
1479 |
|
|
1480 |
42 |
rolesList = [rolenames.ROLE_OPTION_PANE, \ |
1481 |
|
rolenames.ROLE_DIALOG, \ |
1482 |
|
rolenames.ROLE_APPLICATION] |
1483 |
42 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1484 |
0 |
pane = event.source |
1485 |
0 |
if pane.name.startswith(_("Spellcheck:")): |
1486 |
0 |
debug.println(self.debugLevel, |
1487 |
0 |
"StarOffice.onNameChanged - Writer: spell check dialog.") |
1488 |
|
|
1489 |
0 |
self.readMisspeltWord(event, pane) |
1490 |
|
|
1491 |
|
# Fall-thru to process the event with the default handler. |
1492 |
|
|
1493 |
|
# Pass the event onto the parent class to be handled in the default way. |
1494 |
|
|
1495 |
42 |
default.Script.onNameChanged(self, event) |
1496 |
|
|
1497 |
|
|
1498 |
1 |
def onFocus(self, event): |
1499 |
|
"""Called whenever an object gets focus. Overridden in this script |
1500 |
|
so that we can adjust "focus:" events for children of a combo-box |
1501 |
|
to just set the focus to the combo box. This is needed to help |
1502 |
|
reduce the verbosity of focusing on the Calc Name combo box (see |
1503 |
|
bug #364407). |
1504 |
|
|
1505 |
|
Arguments: |
1506 |
|
- event: the Event |
1507 |
|
""" |
1508 |
|
|
1509 |
155 |
if event.source.parent.role == rolenames.ROLE_COMBO_BOX: |
1510 |
0 |
orca.setLocusOfFocus(None, event.source.parent, False) |
1511 |
0 |
return |
1512 |
|
|
1513 |
|
# If we are FOCUSED on a paragraph inside a table cell (in Writer), |
1514 |
|
# then speak/braille that parent table cell (see bug #382415). |
1515 |
|
# |
1516 |
155 |
if event.source.role == rolenames.ROLE_PARAGRAPH and \ |
1517 |
|
event.source.parent.role == rolenames.ROLE_TABLE_CELL and \ |
1518 |
|
event.source.state.count(atspi.Accessibility.STATE_FOCUSED): |
1519 |
0 |
if self.lastCell != event.source.parent: |
1520 |
0 |
default.Script.locusOfFocusChanged(self, event, |
1521 |
0 |
None, event.source.parent) |
1522 |
0 |
self.lastCell = event.source.parent |
1523 |
0 |
return |
1524 |
|
|
1525 |
155 |
default.Script.onFocus(self, event) |
1526 |
|
|
1527 |
1 |
def onStateChanged(self, event): |
1528 |
|
"""Called whenever an object's state changes. |
1529 |
|
|
1530 |
|
Arguments: |
1531 |
|
- event: the Event |
1532 |
|
""" |
1533 |
|
|
1534 |
|
# If this is a state change "focused" event that we care about, and |
1535 |
|
# we are in Writer, check to see if we are entering or leaving a table. |
1536 |
|
# |
1537 |
563 |
if event.type == "object:state-changed:focused" and event.detail1 == 1: |
1538 |
63 |
current = event.source.parent |
1539 |
317 |
while current.role != rolenames.ROLE_APPLICATION: |
1540 |
301 |
if current.role == rolenames.ROLE_FRAME and \ |
1541 |
|
(current.name and current.name.endswith(_("Writer"))): |
1542 |
47 |
self.checkForTableBoundry(orca_state.locusOfFocus, |
1543 |
47 |
event.source) |
1544 |
47 |
break |
1545 |
254 |
current = current.parent |
1546 |
|
|
1547 |
|
# Prevent "object:state-changed:active" events from activating |
1548 |
|
# the find operation. See comment #18 of bug #354463. |
1549 |
|
# |
1550 |
551 |
if event.type == "object:state-changed:active": |
1551 |
23 |
if self.findCommandRun: |
1552 |
0 |
return |
1553 |
|
|
1554 |
|
# [[[TODO: JD - HACK because we won't get events from toggle |
1555 |
|
# buttons on the Formatting toolbar until we "tickle/poke" |
1556 |
|
# the hierarchy. But we only want to do it once. |
1557 |
|
# See bug #363830 and OOo issue #70872.]]] |
1558 |
|
# |
1559 |
23 |
if not self.tickled: |
1560 |
4 |
self.tickled = self.flatReviewContextClass(self) |
1561 |
|
|
1562 |
|
# Announce when the toolbar buttons are toggled if we just toggled |
1563 |
|
# them; not if we navigated to some text. |
1564 |
|
# |
1565 |
551 |
if event.type == "object:state-changed:checked" and \ |
1566 |
|
(event.source.role == rolenames.ROLE_TOGGLE_BUTTON or \ |
1567 |
|
event.source.role == rolenames.ROLE_PUSH_BUTTON): |
1568 |
0 |
weToggledIt = False |
1569 |
0 |
if isinstance(orca_state.lastInputEvent, \ |
1570 |
0 |
input_event.MouseButtonEvent): |
1571 |
0 |
x = orca_state.lastInputEvent.x |
1572 |
0 |
y = orca_state.lastInputEvent.y |
1573 |
0 |
weToggledIt = event.source.component.contains(x, y, 0) |
1574 |
|
|
1575 |
0 |
elif isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent): |
1576 |
0 |
keyString = orca_state.lastInputEvent.event_string |
1577 |
0 |
navKeys = ["Up", "Down", "Page_Up", "Page_Down", "Home", "End"] |
1578 |
0 |
wasCommand = orca_state.lastInputEvent.modifiers \ |
1579 |
|
& (1 << atspi.Accessibility.MODIFIER_CONTROL \ |
1580 |
|
| 1 << atspi.Accessibility.MODIFIER_ALT \ |
1581 |
|
| 1 << atspi.Accessibility.MODIFIER_META \ |
1582 |
|
| 1 << atspi.Accessibility.MODIFIER_META2 \ |
1583 |
|
| 1 << atspi.Accessibility.MODIFIER_META3) |
1584 |
0 |
weToggledIt = wasCommand and keyString not in navKeys |
1585 |
|
|
1586 |
0 |
if weToggledIt: |
1587 |
0 |
speech.speakUtterances(self.speechGenerator.getSpeech( \ |
1588 |
|
event.source, False)) |
1589 |
|
|
1590 |
|
# If we are FOCUSED on a paragraph inside a table cell (in Writer), |
1591 |
|
# then speak/braille that parent table cell (see bug #382415). |
1592 |
|
# |
1593 |
551 |
if event.type == "object:state-changed:focused" and \ |
1594 |
|
event.source.role == rolenames.ROLE_PARAGRAPH and \ |
1595 |
|
event.source.parent.role == rolenames.ROLE_TABLE_CELL and \ |
1596 |
|
event.detail1 == 0 and \ |
1597 |
|
event.source.state.count(atspi.Accessibility.STATE_FOCUSED): |
1598 |
|
|
1599 |
|
# Check to see if the last input event was "Up" or "Down". |
1600 |
|
# If it was, and we are in the same table cell as last time, |
1601 |
|
# and if that table cell has more than one child, then just |
1602 |
|
# get the speech for that single child, otherwise speak/braille |
1603 |
|
# the parent table cell. |
1604 |
|
# |
1605 |
0 |
event_string = None |
1606 |
0 |
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent): |
1607 |
0 |
event_string = orca_state.lastInputEvent.event_string |
1608 |
0 |
if (event_string == "Up" or event_string == "Down") and \ |
1609 |
|
event.source.parent == self.lastCell and \ |
1610 |
|
event.source.parent.childCount > 1: |
1611 |
0 |
default.Script.locusOfFocusChanged(self, event, |
1612 |
0 |
None, event.source) |
1613 |
|
else: |
1614 |
0 |
default.Script.locusOfFocusChanged(self, event, |
1615 |
0 |
None, event.source.parent) |
1616 |
0 |
self.lastCell = event.source.parent |
1617 |
0 |
return |
1618 |
|
|
1619 |
|
# Two events are received when the caret moves |
1620 |
|
# to a new paragraph. The first is a focus event |
1621 |
|
# (in the form of object:state-changed:focused |
1622 |
|
# instead of focus:). The second is a caret-moved |
1623 |
|
# event. Just set the locusOfFocus for the first event. |
1624 |
|
# |
1625 |
551 |
if event.type == "object:state-changed:focused" and \ |
1626 |
|
event.source.role == rolenames.ROLE_PARAGRAPH and \ |
1627 |
|
event.source != self.currentParagraph: |
1628 |
38 |
self.currentParagraph = event.source |
1629 |
38 |
orca.setLocusOfFocus(event, event.source, False) |
1630 |
38 |
return |
1631 |
|
|
1632 |
|
# If we get "object:state-changed:focused" events for children of |
1633 |
|
# a combo-box, just set the focus to the combo box. This is needed |
1634 |
|
# to help reduce the verbosity of focusing on the Calc Name combo |
1635 |
|
# box (see bug #364407). |
1636 |
|
# |
1637 |
513 |
if event.source.parent and \ |
1638 |
|
event.source.parent.role == rolenames.ROLE_COMBO_BOX: |
1639 |
1 |
orca.setLocusOfFocus(None, event.source.parent, False) |
1640 |
1 |
return |
1641 |
|
|
1642 |
512 |
default.Script.onStateChanged(self, event) |
1643 |
|
|
1644 |
|
|
1645 |
|
# This method tries to detect and handle the following cases: |
1646 |
|
# 1) Calc: spread sheet Name Box line. |
1647 |
|
|
1648 |
1 |
def onSelectionChanged(self, event): |
1649 |
|
"""Called when an object's selection changes. |
1650 |
|
|
1651 |
|
Arguments: |
1652 |
|
- event: the Event |
1653 |
|
""" |
1654 |
|
|
1655 |
33 |
debug.printObjectEvent(self.debugLevel, |
1656 |
33 |
event, |
1657 |
33 |
event.source.toString()) |
1658 |
|
|
1659 |
|
# self.printAncestry(event.source) |
1660 |
|
|
1661 |
|
# 1) Calc: spread sheet input line. |
1662 |
|
# |
1663 |
|
# If this "object:selection-changed" is for the spread sheet Name |
1664 |
|
# Box, then check to see if the current locus of focus is a spread |
1665 |
|
# sheet cell. If it is, and the contents of the input line are |
1666 |
|
# different from what is displayed in that cell, then speak "has |
1667 |
|
# formula" and append it to the braille line. |
1668 |
|
# |
1669 |
33 |
rolesList = [rolenames.ROLE_LIST, \ |
1670 |
|
rolenames.ROLE_COMBO_BOX, \ |
1671 |
|
rolenames.ROLE_PANEL, \ |
1672 |
|
rolenames.ROLE_TOOL_BAR, \ |
1673 |
|
rolenames.ROLE_PANEL, \ |
1674 |
|
rolenames.ROLE_ROOT_PANE, \ |
1675 |
|
rolenames.ROLE_FRAME, \ |
1676 |
|
rolenames.ROLE_APPLICATION] |
1677 |
33 |
if self.isDesiredFocusedItem(event.source, rolesList): |
1678 |
0 |
if orca_state.locusOfFocus.role == rolenames.ROLE_TABLE_CELL: |
1679 |
0 |
cell = orca_state.locusOfFocus |
1680 |
|
|
1681 |
|
# We are getting two "object:selection-changed" events |
1682 |
|
# for each spread sheet cell move, so in order to prevent |
1683 |
|
# appending "has formula" twice, we only do it if the last |
1684 |
|
# cell is different from this one. |
1685 |
|
# |
1686 |
0 |
if cell != self.lastCell: |
1687 |
0 |
self.lastCell = cell |
1688 |
|
|
1689 |
0 |
if cell.text: |
1690 |
0 |
cellText = self.getText(cell, 0, -1) |
1691 |
0 |
if cellText and len(cellText): |
1692 |
0 |
if inputLineForCell and inputLineForCell.text: |
1693 |
0 |
inputLine = self.getText(inputLineForCell,0,-1) |
1694 |
0 |
if inputLine and \ |
1695 |
|
(len(inputLine) > 1) and \ |
1696 |
|
(inputLine[0] == "="): |
1697 |
0 |
hf = _(" has formula") |
1698 |
0 |
speech.speak(hf, None, False) |
1699 |
|
|
1700 |
0 |
line = braille.getShowingLine() |
1701 |
0 |
line.addRegion(braille.Region(hf)) |
1702 |
0 |
braille.refresh() |
1703 |
|
# |
1704 |
|
# Fall-thru to process the event with |
1705 |
|
# the default handler. |
1706 |
|
|
1707 |
33 |
default.Script.onSelectionChanged(self, event) |
1708 |
|
|
1709 |
1 |
def getText(self, obj, startOffset, endOffset): |
1710 |
|
"""Returns the substring of the given object's text specialization. |
1711 |
|
|
1712 |
|
NOTE: This is here to handle the problematic implementation of |
1713 |
|
getText by OpenOffice. See the bug discussion at: |
1714 |
|
|
1715 |
|
http://bugzilla.gnome.org/show_bug.cgi?id=356425) |
1716 |
|
|
1717 |
|
Once the OpenOffice issue has been resolved, this method probably |
1718 |
|
should be removed. |
1719 |
|
|
1720 |
|
Arguments: |
1721 |
|
- obj: an accessible supporting the accessible text specialization |
1722 |
|
- startOffset: the starting character position |
1723 |
|
- endOffset: the ending character position |
1724 |
|
""" |
1725 |
59 |
text = obj.text.getText(0, -1).decode("UTF-8") |
1726 |
59 |
if startOffset >= len(text): |
1727 |
0 |
startOffset = len(text) - 1 |
1728 |
59 |
if endOffset == -1: |
1729 |
12 |
endOffset = len(text) |
1730 |
47 |
elif startOffset >= endOffset: |
1731 |
0 |
endOffset = startOffset + 1 |
1732 |
59 |
string = text[max(0,startOffset):min(len(text),endOffset)] |
1733 |
59 |
string = string.encode("UTF-8") |
1734 |
59 |
return string |
1735 |
|
|
1736 |
1 |
def speakCellName(self, name): |
1737 |
|
"""Speaks the given cell name. |
1738 |
|
|
1739 |
|
Arguments: |
1740 |
|
- name: the name of the cell |
1741 |
|
""" |
1742 |
|
|
1743 |
0 |
line = _("Cell ") + name |
1744 |
0 |
speech.speak(line) |
1745 |
|
|
1746 |
1 |
def onCaretMoved(self, event): |
1747 |
|
"""Called whenever the caret moves. |
1748 |
|
|
1749 |
|
Arguments: |
1750 |
|
- event: the Event |
1751 |
|
""" |
1752 |
|
|
1753 |
|
# If we are FOCUSED on a paragraph inside a table cell (in Writer), |
1754 |
|
# then just return (modulo the special cases below). Speaking and |
1755 |
|
# brailling will have been done in the onStateChanged() routine |
1756 |
|
# (see bug #382415). |
1757 |
|
# |
1758 |
196 |
if event.source.role == rolenames.ROLE_PARAGRAPH and \ |
1759 |
|
event.source.parent.role == rolenames.ROLE_TABLE_CELL and \ |
1760 |
|
event.source.state.count(atspi.Accessibility.STATE_FOCUSED): |
1761 |
0 |
event_string = orca_state.lastInputEvent.event_string |
1762 |
|
|
1763 |
|
# If we are moving up and down, and we are speaking-by-cell |
1764 |
|
# (as opposed to by-row), then speak the cell name. Otherwise |
1765 |
|
# just return. |
1766 |
|
# |
1767 |
0 |
if (event_string == "Up" or event_string == "Down"): |
1768 |
0 |
if settings.readTableCellRow == False: |
1769 |
0 |
if event.detail1 != -1: |
1770 |
0 |
self.speakCellName(event.source.parent.name) |
1771 |
0 |
return |
1772 |
|
|
1773 |
|
# If we are moving left or right and we are in a new cell, just |
1774 |
|
# return. |
1775 |
|
# |
1776 |
0 |
if (event_string == "Left" or event_string == "Right") and \ |
1777 |
|
self.lastCell != event.source.parent: |
1778 |
0 |
return |
1779 |
|
|
1780 |
0 |
caretOffset = event.source.text.caretOffset |
1781 |
0 |
len = event.source.text.characterCount |
1782 |
|
|
1783 |
|
# If you are in a table cell and you arrow Right, the caret |
1784 |
|
# will focus at the end of the current paragraph before moving |
1785 |
|
# into the next cell. To be similar to the way that caret |
1786 |
|
# navigation works in other paragraphs in OOo, just return. |
1787 |
|
# |
1788 |
0 |
if event_string == "Right" and caretOffset == len: |
1789 |
0 |
return |
1790 |
|
|
1791 |
|
# If we have moved left and the caret position is at the end of |
1792 |
|
# the paragraph or if we have moved right and the caret position |
1793 |
|
# is at the start of the text string, or the last key input was |
1794 |
|
# Tab or Shift-Tab, and if we are speaking-by-cell (as opposed |
1795 |
|
# to by-row), then speak the cell name, otherwise just return |
1796 |
|
# (see bug #382418). |
1797 |
|
# |
1798 |
0 |
if (event_string == "Left" and caretOffset == len) or \ |
1799 |
|
(event_string == "Right" and caretOffset == 0) or \ |
1800 |
|
(event_string == "Tab" or event_string == "ISO_Left_Tab"): |
1801 |
0 |
if settings.readTableCellRow == False: |
1802 |
0 |
if event.detail1 != -1: |
1803 |
0 |
self.speakCellName(event.source.parent.name) |
1804 |
|
|
1805 |
|
# Speak a blank line, if appropriate. |
1806 |
0 |
if self.speakBlankLine(event.source): |
1807 |
0 |
speech.speak(_("blank"), None, False) |
1808 |
0 |
return |
1809 |
|
|
1810 |
|
# Speak a newline, if appropriate. |
1811 |
196 |
if self.speakNewLine(event.source): |
1812 |
12 |
speech.speak(chnames.getCharacterName("\n"), None, False) |
1813 |
|
|
1814 |
|
# Speak a blank line, if appropriate. |
1815 |
196 |
if self.speakBlankLine(event.source): |
1816 |
10 |
speech.speak(_("blank"), None, False) |
1817 |
|
|
1818 |
196 |
default.Script.onCaretMoved(self, event) |
1819 |
|
|
1820 |
|
|
1821 |
1 |
def speakNewLine(self, obj): |
1822 |
|
"""Returns True if a newline should be spoken. |
1823 |
|
Otherwise, returns False. |
1824 |
|
""" |
1825 |
|
|
1826 |
|
# Get the the AccessibleText interrface. |
1827 |
196 |
text = obj.text |
1828 |
196 |
if not text: |
1829 |
0 |
return False |
1830 |
|
|
1831 |
|
# Was a left or right-arrow key pressed? |
1832 |
196 |
if not (orca_state.lastInputEvent and \ |
1833 |
|
orca_state.lastInputEvent.__dict__.has_key("event_string")): |
1834 |
0 |
return False |
1835 |
|
|
1836 |
196 |
lastKey = orca_state.lastInputEvent.event_string |
1837 |
196 |
if lastKey != "Left" and lastKey != "Right": |
1838 |
160 |
return False |
1839 |
|
|
1840 |
|
# Was a control key pressed? |
1841 |
36 |
mods = orca_state.lastInputEvent.modifiers |
1842 |
36 |
isControlKey = mods & (1 << atspi.Accessibility.MODIFIER_CONTROL) |
1843 |
|
|
1844 |
|
# Get the line containing the caret |
1845 |
36 |
caretOffset = text.caretOffset |
1846 |
36 |
line = text.getTextAtOffset(caretOffset, \ |
1847 |
36 |
atspi.Accessibility.TEXT_BOUNDARY_LINE_START) |
1848 |
36 |
lineStart = line[1] |
1849 |
36 |
lineEnd = line[2] |
1850 |
|
|
1851 |
36 |
if isControlKey: # control-right-arrow or control-left-arrow |
1852 |
|
|
1853 |
|
# Get the word containing the caret. |
1854 |
0 |
word = text.getTextAtOffset(caretOffset, \ |
1855 |
0 |
atspi.Accessibility.TEXT_BOUNDARY_WORD_START) |
1856 |
0 |
wordStart = word[1] |
1857 |
0 |
wordEnd = word[2] |
1858 |
|
|
1859 |
0 |
if lastKey == "Right": |
1860 |
0 |
if wordStart == lineStart: |
1861 |
0 |
return True |
1862 |
|
else: |
1863 |
0 |
if wordEnd == lineEnd: |
1864 |
0 |
return True |
1865 |
|
|
1866 |
|
else: # right arrow or left arrow |
1867 |
|
|
1868 |
36 |
if lastKey == "Right": |
1869 |
18 |
if caretOffset == lineStart: |
1870 |
6 |
return True |
1871 |
|
else: |
1872 |
18 |
if caretOffset == lineEnd: |
1873 |
6 |
return True |
1874 |
|
|
1875 |
24 |
return False |
1876 |
|
|
1877 |
|
|
1878 |
1 |
def speakBlankLine(self, obj): |
1879 |
|
"""Returns True if a blank line should be spoken. |
1880 |
|
Otherwise, returns False. |
1881 |
|
""" |
1882 |
|
|
1883 |
|
# Get the the AccessibleText interrface. |
1884 |
196 |
text = obj.text |
1885 |
196 |
if not text: |
1886 |
0 |
return False |
1887 |
|
|
1888 |
|
# Get the line containing the caret |
1889 |
196 |
caretOffset = text.caretOffset |
1890 |
196 |
line = text.getTextAtOffset(caretOffset, \ |
1891 |
196 |
atspi.Accessibility.TEXT_BOUNDARY_LINE_START) |
1892 |
|
|
1893 |
|
# If this is a blank line, announce it if the user requested |
1894 |
|
# that blank lines be spoken. |
1895 |
196 |
if line[1] == 0 and line[2] == 0: |
1896 |
10 |
return settings.speakBlankLines |
1897 |
|
|