winword.py
63.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
#appModules/winword.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2016 NV Access Limited, Manish Agrawal, Derek Riemer
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import ctypes
import time
from comtypes import COMError, GUID, BSTR
import comtypes.client
import comtypes.automation
import uuid
import operator
import locale
import collections
import colorsys
import sayAllHandler
import eventHandler
import braille
import scriptHandler
import languageHandler
import ui
import NVDAHelper
import XMLFormatting
from logHandler import log
import winUser
import oleacc
import globalVars
import speech
import config
import textInfos
import textInfos.offsets
import colors
import controlTypes
import treeInterceptorHandler
import browseMode
import review
from cursorManager import CursorManager, ReviewCursorManager
from tableUtils import HeaderCellInfo, HeaderCellTracker
from . import Window
from ..behaviors import EditableTextWithoutAutoSelectDetection
#Word constants
#wdLineSpacing rules
wdLineSpaceSingle=0
wdLineSpace1pt5=1
wdLineSpaceDouble=2
wdLineSpaceAtLeast=3
wdLineSpaceExactly=4
wdLineSpaceMultiple=5
# wdMeasurementUnits
wdInches=0
wdCentimeters=1
wdMillimeters=2
wdPoints=3
wdPicas=4
wdCollapseEnd=0
wdCollapseStart=1
#Indexing
wdActiveEndAdjustedPageNumber=1
wdActiveEndPageNumber=3
wdNumberOfPagesInDocument=4
wdHorizontalPositionRelativeToPage=5
wdFirstCharacterLineNumber=10
wdWithInTable=12
wdStartOfRangeRowNumber=13
wdMaximumNumberOfRows=15
wdStartOfRangeColumnNumber=16
wdMaximumNumberOfColumns=18
#Horizontal alignment
wdAlignParagraphLeft=0
wdAlignParagraphCenter=1
wdAlignParagraphRight=2
wdAlignParagraphJustify=3
#Units
wdCharacter=1
wdWord=2
wdSentence=3
wdParagraph=4
wdLine=5
wdStory=6
wdColumn=9
wdRow=10
wdWindow=11
wdCell=12
wdCharFormat=13
wdParaFormat=14
wdTable=15
#GoTo - direction
wdGoToAbsolute=1
wdGoToRelative=2
wdGoToNext=2
wdGoToPrevious=3
#GoTo - units
wdGoToBookmark=-1
wdGoToSection=0
wdGoToPage=1
wdGoToTable=2
wdGoToLine=3
wdGoToFootnote=4
wdGoToEndnote=5
wdGoToComment=6
wdGoToField=7
wdGoToGraphic=8
wdGoToObject=9
wdGoToEquation=10
wdGoToHeading=11
wdGoToPercent=12
wdGoToSpellingError=13
wdGoToGrammaticalError=14
wdGoToProofreadingError=15
wdCommentsStory=4
wdEndnotesStory=3
wdEvenPagesFooterStory=8
wdEvenPagesHeaderStory=6
wdFirstPageFooterStory=11
wdFirstPageHeaderStory=10
wdFootnotesStory=2
wdMainTextStory=1
wdPrimaryFooterStory=9
wdPrimaryHeaderStory=7
wdTextFrameStory=5
wdFieldFormTextInput=70
wdFieldFormCheckBox=71
wdFieldFormDropDown=83
wdContentControlRichText=0
wdContentControlText=1
wdContentControlPicture=2
wdContentControlComboBox=3
wdContentControlDropdownList=4
wdContentControlBuildingBlockGallery=5
wdContentControlDate=6
wdContentControlGroup=7
wdContentControlCheckBox=8
wdNoRevision=0
wdRevisionInsert=1
wdRevisionDelete=2
wdRevisionProperty=3
wdRevisionParagraphNumber=4
wdRevisionDisplayField=5
wdRevisionReconcile=6
wdRevisionConflict=7
wdRevisionStyle=8
wdRevisionReplace=9
wdRevisionParagraphProperty=10
wdRevisionTableProperty=11
wdRevisionSectionProperty=12
wdRevisionStyleDefinition=13
wdRevisionMovedFrom=14
wdRevisionMovedTo=15
wdRevisionCellInsertion=16
wdRevisionCellDeletion=17
wdRevisionCellMerge=18
# MsoThemeColorSchemeIndex
msoThemeAccent1=5
msoThemeAccent2=6
msoThemeAccent3=7
msoThemeAccent4=8
msoThemeAccent5=9
msoThemeAccent6=10
msoThemeDark1=1
msoThemeDark2=3
msoThemeFollowedHyperlink=12
msoThemeHyperlink=11
msoThemeLight1=2
msoThemeLight2=4
# WdThemeColorIndex
wdNotThemeColor=-1
wdThemeColorAccent1=4
wdThemeColorAccent2=5
wdThemeColorAccent3=6
wdThemeColorAccent4=7
wdThemeColorAccent5=8
wdThemeColorAccent6=9
wdThemeColorBackground1=12
wdThemeColorBackground2=14
wdThemeColorHyperlink=10
wdThemeColorHyperlinkFollowed=11
wdThemeColorMainDark1=0
wdThemeColorMainDark2=2
wdThemeColorMainLight1=1
wdThemeColorMainLight2=3
wdThemeColorText1=13
wdThemeColorText2=15
# Word Field types
FIELD_TYPE_REF = 3 # cross reference field
FIELD_TYPE_HYPERLINK = 88 # hyperlink field
# Mapping from http://www.wordarticles.com/Articles/Colours/2007.php#UIConsiderations
WdThemeColorIndexToMsoThemeColorSchemeIndex={
wdThemeColorMainDark1:msoThemeDark1,
wdThemeColorMainLight1:msoThemeLight1,
wdThemeColorMainDark2:msoThemeDark2,
wdThemeColorMainLight2:msoThemeLight2,
wdThemeColorAccent1:msoThemeAccent1,
wdThemeColorAccent2:msoThemeAccent2,
wdThemeColorAccent3:msoThemeAccent3,
wdThemeColorAccent4:msoThemeAccent4,
wdThemeColorAccent5:msoThemeAccent5,
wdThemeColorAccent6:msoThemeAccent6,
wdThemeColorHyperlink:msoThemeHyperlink,
wdThemeColorHyperlinkFollowed:msoThemeFollowedHyperlink,
wdThemeColorBackground1:msoThemeLight1,
wdThemeColorText1:msoThemeDark1,
wdThemeColorBackground2:msoThemeLight2,
wdThemeColorText2:msoThemeDark2,
}
wdRevisionTypeLabels={
# Translators: a Microsoft Word revision type (inserted content)
wdRevisionInsert:_("insertion"),
# Translators: a Microsoft Word revision type (deleted content)
wdRevisionDelete:_("deletion"),
# Translators: a Microsoft Word revision type (changed content property, e.g. font, color)
wdRevisionProperty:_("property"),
# Translators: a Microsoft Word revision type (changed paragraph number)
wdRevisionParagraphNumber:_("paragraph number"),
# Translators: a Microsoft Word revision type (display field)
wdRevisionDisplayField:_("display field"),
# Translators: a Microsoft Word revision type (reconcile)
wdRevisionReconcile:_("reconcile"),
# Translators: a Microsoft Word revision type (conflicting revision)
wdRevisionConflict:_("conflict"),
# Translators: a Microsoft Word revision type (style change)
wdRevisionStyle:_("style"),
# Translators: a Microsoft Word revision type (replaced content)
wdRevisionReplace:_("replace"),
# Translators: a Microsoft Word revision type (changed paragraph property, e.g. alignment)
wdRevisionParagraphProperty:_("paragraph property"),
# Translators: a Microsoft Word revision type (table)
wdRevisionTableProperty:_("table property"),
# Translators: a Microsoft Word revision type (section property)
wdRevisionSectionProperty:_("section property"),
# Translators: a Microsoft Word revision type (style definition)
wdRevisionStyleDefinition:_("style definition"),
# Translators: a Microsoft Word revision type (moved from)
wdRevisionMovedFrom:_("moved from"),
# Translators: a Microsoft Word revision type (moved to)
wdRevisionMovedTo:_("moved to"),
# Translators: a Microsoft Word revision type (inserted table cell)
wdRevisionCellInsertion:_("cell insertion"),
# Translators: a Microsoft Word revision type (deleted table cell)
wdRevisionCellDeletion:_("cell deletion"),
# Translators: a Microsoft Word revision type (merged table cells)
wdRevisionCellMerge:_("cell merge"),
}
storyTypeLocalizedLabels={
wdCommentsStory:_("Comments"),
wdEndnotesStory:_("Endnotes"),
wdEvenPagesFooterStory:_("Even pages footer"),
wdEvenPagesHeaderStory:_("Even pages header"),
wdFirstPageFooterStory:_("First page footer"),
wdFirstPageHeaderStory:_("First page header"),
wdFootnotesStory:_("Footnotes"),
wdPrimaryFooterStory:_("Primary footer"),
wdPrimaryHeaderStory:_("Primary header"),
wdTextFrameStory:_("Text frame"),
}
wdFieldTypesToNVDARoles={
wdFieldFormTextInput:controlTypes.ROLE_EDITABLETEXT,
wdFieldFormCheckBox:controlTypes.ROLE_CHECKBOX,
wdFieldFormDropDown:controlTypes.ROLE_COMBOBOX,
}
wdContentControlTypesToNVDARoles={
wdContentControlRichText:controlTypes.ROLE_EDITABLETEXT,
wdContentControlText:controlTypes.ROLE_EDITABLETEXT,
wdContentControlPicture:controlTypes.ROLE_GRAPHIC,
wdContentControlComboBox:controlTypes.ROLE_COMBOBOX,
wdContentControlDropdownList:controlTypes.ROLE_COMBOBOX,
wdContentControlDate:controlTypes.ROLE_EDITABLETEXT,
wdContentControlGroup:controlTypes.ROLE_GROUPING,
wdContentControlCheckBox:controlTypes.ROLE_CHECKBOX,
}
winwordWindowIid=GUID('{00020962-0000-0000-C000-000000000046}')
wm_winword_expandToLine=ctypes.windll.user32.RegisterWindowMessageW(u"wm_winword_expandToLine")
NVDAUnitsToWordUnits={
textInfos.UNIT_CHARACTER:wdCharacter,
textInfos.UNIT_WORD:wdWord,
textInfos.UNIT_LINE:wdLine,
textInfos.UNIT_SENTENCE:wdSentence,
textInfos.UNIT_PARAGRAPH:wdParagraph,
textInfos.UNIT_TABLE:wdTable,
textInfos.UNIT_CELL:wdCell,
textInfos.UNIT_ROW:wdRow,
textInfos.UNIT_COLUMN:wdColumn,
textInfos.UNIT_STORY:wdStory,
textInfos.UNIT_READINGCHUNK:wdSentence,
}
formatConfigFlagsMap={
"reportFontName":0x1,
"reportFontSize":0x2,
"reportFontAttributes":0x4,
"reportColor":0x8,
"reportAlignment":0x10,
"reportStyle":0x20,
"reportSpellingErrors":0x40,
"reportPage":0x80,
"reportLineNumber":0x100,
"reportTables":0x200,
"reportLists":0x400,
"reportLinks":0x800,
"reportComments":0x1000,
"reportHeadings":0x2000,
"autoLanguageSwitching":0x4000,
"reportRevisions":0x8000,
"reportParagraphIndentation":0x10000,
"reportLineSpacing":0x40000,
}
formatConfigFlag_includeLayoutTables=0x20000
class WordDocumentHeadingQuickNavItem(browseMode.TextInfoQuickNavItem):
def __init__(self,nodeType,document,textInfo,level):
self.level=level
super(WordDocumentHeadingQuickNavItem,self).__init__(nodeType,document,textInfo)
def isChild(self,parent):
if not isinstance(parent,WordDocumentHeadingQuickNavItem):
return False
return self.level>parent.level
class WordDocumentCollectionQuickNavItem(browseMode.TextInfoQuickNavItem):
"""
A QuickNavItem representing an item that MS Word stores as a collection (e.g. link, table etc).
"""
def rangeFromCollectionItem(self,item):
"""
Fetches a Microsoft Word range object from a Microsoft Word item in a collection. E.g. a HyperLink object.
@param item: an item from a collection (E.g. a HyperLink object).
"""
return item.range
def __init__(self,itemType,document,collectionItem):
"""
See L{TextInfoQuickNavItem} for itemType and document argument definitions.
@param collectionItem: an item from an MS Word collection e.g. HyperLink object.
"""
self.collectionItem=collectionItem
self.rangeObj=self.rangeFromCollectionItem(collectionItem)
textInfo=BrowseModeWordDocumentTextInfo(document,None,_rangeObj=self.rangeObj)
super(WordDocumentCollectionQuickNavItem,self).__init__(itemType,document,textInfo)
class WordDocumentCommentQuickNavItem(WordDocumentCollectionQuickNavItem):
@property
def label(self):
author=self.collectionItem.author
date=self.collectionItem.date
text=self.collectionItem.range.text
return _(u"comment: {text} by {author} on {date}").format(author=author,text=text,date=date)
def rangeFromCollectionItem(self,item):
return item.scope
class WordDocumentFieldQuickNavItem(WordDocumentCollectionQuickNavItem):
def rangeFromCollectionItem(self,item):
return item.result
class WordDocumentRevisionQuickNavItem(WordDocumentCollectionQuickNavItem):
@property
def label(self):
revisionType=wdRevisionTypeLabels.get(self.collectionItem.type)
author=self.collectionItem.author or ""
date=self.collectionItem.date
description=self.collectionItem.formatDescription or ""
text=(self.collectionItem.range.text or "")[:100]
return _(u"{revisionType} {description}: {text} by {author} on {date}").format(revisionType=revisionType,author=author,text=text,date=date,description=description)
class WinWordCollectionQuicknavIterator(object):
"""
Allows iterating over an MS Word collection (e.g. HyperLinks) emitting L{QuickNavItem} objects.
"""
quickNavItemClass=WordDocumentCollectionQuickNavItem #: the QuickNavItem class that should be instanciated and emitted.
def __init__(self,itemType,document,direction,rangeObj,includeCurrent):
"""
See L{QuickNavItemIterator} for itemType, document and direction definitions.
@param rangeObj: a Microsoft Word range object where the collection should be fetched from.
@ param includeCurrent: if true then any item at the initial position will be also emitted rather than just further ones.
"""
self.document=document
self.itemType=itemType
self.direction=direction if direction else "next"
self.rangeObj=rangeObj
self.includeCurrent=includeCurrent
def collectionFromRange(self,rangeObj):
"""
Fetches a Microsoft Word collection object from a Microsoft Word range object. E.g. HyperLinks from a range.
@param rangeObj: a Microsoft Word range object.
@return: a Microsoft Word collection object.
"""
raise NotImplementedError
def filter(self,item):
"""
Only allows certain items fom a collection to be emitted. E.g. a table who's borders are enabled.
@param item: an item from a Microsoft Word collection (e.g. HyperLink object).
@return True if this item should be allowd, false otherwise.
@rtype: bool
"""
return True
def iterate(self):
"""
returns a generator that emits L{QuickNavItem} objects for this collection.
"""
if self.direction=="next":
self.rangeObj.moveEnd(wdStory,1)
elif self.direction=="previous":
self.rangeObj.collapse(wdCollapseStart)
self.rangeObj.moveStart(wdStory,-1)
items=self.collectionFromRange(self.rangeObj)
itemCount=items.count
isFirst=True
for index in xrange(1,itemCount+1):
if self.direction=="previous":
index=itemCount-(index-1)
collectionItem=items[index]
try:
item=self.quickNavItemClass(self.itemType,self.document,collectionItem)
except COMError:
message = ("Error iterating over item with "
"type: {type}, iteration direction: {dir}, total item count: {count}, item at index: {index}"
"\nThis could be caused by an issue with some element within or a corruption of the word document."
).format(type=self.itemType, dir=self.direction, count=itemCount, index=index)
log.debugWarning(message ,exc_info=True)
continue
itemRange=item.rangeObj
# Skip over the item we're already on.
if not self.includeCurrent and isFirst and ((self.direction=="next" and itemRange.start<=self.rangeObj.start) or (self.direction=="previous" and itemRange.end>self.rangeObj.end)):
continue
if not self.filter(collectionItem):
continue
yield item
isFirst=False
class LinkWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator):
quickNavItemClass=WordDocumentFieldQuickNavItem
def collectionFromRange(self,rangeObj):
return rangeObj.fields
def filter(self, item):
t = item.type
if t == FIELD_TYPE_REF:
fieldText = item.code.text.strip().split(' ')
# ensure that the text has a \\h in it
return any( fieldText[i] == '\\h' for i in xrange(2, len(fieldText)) )
return t == FIELD_TYPE_HYPERLINK
class CommentWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator):
quickNavItemClass=WordDocumentCommentQuickNavItem
def collectionFromRange(self,rangeObj):
return rangeObj.comments
class RevisionWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator):
quickNavItemClass=WordDocumentRevisionQuickNavItem
def collectionFromRange(self,rangeObj):
return rangeObj.revisions
class GraphicWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator):
def collectionFromRange(self,rangeObj):
return rangeObj.inlineShapes
def filter(self,item):
return 2<item.type<5
class TableWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator):
def collectionFromRange(self,rangeObj):
return rangeObj.tables
def filter(self,item):
return item.borders.enable
class WordDocumentTextInfo(textInfos.TextInfo):
# #4852: temporary fix.
# force mouse reading chunk to sentense to make it what it used to be in 2014.4.
# We need to however fix line so it does not accidentially scroll.
def _get_unit_mouseChunk(self):
unit=super(WordDocumentTextInfo,self).unit_mouseChunk
if unit==textInfos.UNIT_LINE:
unit=textInfos.UNIT_SENTENCE
return unit
def copyToClipboard(self):
self._rangeObj.copy()
return True
def find(self,text,caseSensitive=False,reverse=False):
f=self._rangeObj.find
f.text=text
f.matchCase=caseSensitive
f.forward=not reverse
return f.execute()
shouldIncludeLayoutTables=True #: layout tables should always be included (no matter the user's browse mode setting).
def activate(self):
import mathPres
mathMl=mathPres.getMathMlFromTextInfo(self)
if mathMl:
return mathPres.interactWithMathMl(mathMl)
# Handle activating links.
# It is necessary to expand to word to get a link as the link's first character is never actually in the link!
tempRange=self._rangeObj.duplicate
tempRange.expand(wdWord)
links=tempRange.hyperlinks
if links.count>0:
links[1].follow()
return
tempRange.expand(wdParagraph)
fields=tempRange.fields
for field in (fields.item(i) for i in xrange(1, fields.count+1)):
if field.type != FIELD_TYPE_REF:
continue
fResult = field.result
fResult.moveStart(wdCharacter,-1) # move back one visible character (passed the hidden text eg the code for the reference).
fResStart = fResult.start +1 # don't include the character before the hidden text.
fResEnd = fResult.end
rObjStart = self._rangeObj.start
rObjEnd = self._rangeObj.end
# check to see if the _rangeObj is inside the fResult range
if not (fResStart <= rObjStart and fResEnd >= rObjEnd):
continue
# text will be something like ' REF _Ref457210120 \\h '
fieldText = field.code.text.strip().split(' ')
# the \\h field indicates that the field is a link
if not any( fieldText[i] == '\\h' for i in xrange(2, len(fieldText)) ):
log.debugWarning("no \\h for field xref: %s" % field.code.text)
continue
bookmarkKey = fieldText[1] # we want the _Ref12345 part
# get book mark start, we need to look at the whole document to find the bookmark.
tempRange.Expand(wdStory)
bMark = tempRange.bookmarks(bookmarkKey)
self._rangeObj.setRange(bMark.start, bMark.start)
self.updateCaret()
tiCopy = self.copy()
tiCopy.expand(textInfos.UNIT_LINE)
speech.speakTextInfo(tiCopy,reason=controlTypes.REASON_FOCUS)
braille.handler.handleCaretMove(self)
return
def _expandToLineAtCaret(self):
lineStart=ctypes.c_int()
lineEnd=ctypes.c_int()
res=NVDAHelper.localLib.nvdaInProcUtils_winword_expandToLine(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,self._rangeObj.start,ctypes.byref(lineStart),ctypes.byref(lineEnd))
if res!=0 or lineStart.value==lineEnd.value or lineStart.value==-1 or lineEnd.value==-1:
log.debugWarning("winword_expandToLine failed")
self._rangeObj.expand(wdParagraph)
return
self._rangeObj.setRange(lineStart.value,lineEnd.value)
def __init__(self,obj,position,_rangeObj=None):
super(WordDocumentTextInfo,self).__init__(obj,position)
if _rangeObj:
self._rangeObj=_rangeObj.Duplicate
return
if isinstance(position,textInfos.Point):
try:
self._rangeObj=self.obj.WinwordDocumentObject.activeWindow.RangeFromPoint(position.x,position.y)
except COMError:
raise NotImplementedError
elif position==textInfos.POSITION_SELECTION:
self._rangeObj=self.obj.WinwordSelectionObject.range
elif position==textInfos.POSITION_CARET:
self._rangeObj=self.obj.WinwordSelectionObject.range
self._rangeObj.Collapse()
elif position==textInfos.POSITION_ALL:
self._rangeObj=self.obj.WinwordSelectionObject.range
self._rangeObj.Expand(wdStory)
elif position==textInfos.POSITION_FIRST:
self._rangeObj=self.obj.WinwordSelectionObject.range
self._rangeObj.SetRange(0,0)
elif position==textInfos.POSITION_LAST:
self._rangeObj=self.obj.WinwordSelectionObject.range
self._rangeObj.endOf(wdStory)
self._rangeObj.move(wdCharacter,-1)
elif isinstance(position,textInfos.offsets.Offsets):
self._rangeObj=self.obj.WinwordSelectionObject.range
self._rangeObj.SetRange(position.startOffset,position.endOffset)
elif isinstance(position,WordDocumentTextInfo):
# copying from one textInfo to another
self._rangeObj=position._rangeObj.duplicate
else:
raise NotImplementedError("position: %s"%position)
def getTextWithFields(self,formatConfig=None):
if self.isCollapsed: return []
if self.obj.ignoreFormatting:
return [self.text]
extraDetail=formatConfig.get('extraDetail',False) if formatConfig else False
if not formatConfig:
formatConfig=config.conf['documentFormatting']
formatConfig['autoLanguageSwitching']=config.conf['speech'].get('autoLanguageSwitching',False)
startOffset=self._rangeObj.start
endOffset=self._rangeObj.end
text=BSTR()
formatConfigFlags=sum(y for x,y in formatConfigFlagsMap.iteritems() if formatConfig.get(x,False))
if self.shouldIncludeLayoutTables:
formatConfigFlags+=formatConfigFlag_includeLayoutTables
if self.obj.ignoreEditorRevisions:
formatConfigFlags&=~formatConfigFlagsMap['reportRevisions']
res=NVDAHelper.localLib.nvdaInProcUtils_winword_getTextInRange(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,startOffset,endOffset,formatConfigFlags,ctypes.byref(text))
if res or not text:
log.debugWarning("winword_getTextInRange failed with %d"%res)
return [self.text]
commandList=XMLFormatting.XMLTextParser().parse(text.value)
for index,item in enumerate(commandList):
if isinstance(item,textInfos.FieldCommand):
field=item.field
if isinstance(field,textInfos.ControlField):
item.field=self._normalizeControlField(field)
elif isinstance(field,textInfos.FormatField):
item.field=self._normalizeFormatField(field,extraDetail=extraDetail)
elif index>0 and isinstance(item,basestring) and item.isspace():
#2047: don't expose language for whitespace as its incorrect for east-asian languages
lastItem=commandList[index-1]
if isinstance(lastItem,textInfos.FieldCommand) and isinstance(lastItem.field,textInfos.FormatField):
try:
del lastItem.field['language']
except KeyError:
pass
return commandList
def _normalizeControlField(self,field):
role=field.pop('role',None)
if role=="heading":
role=controlTypes.ROLE_HEADING
elif role=="table":
role=controlTypes.ROLE_TABLE
field['table-rowcount']=int(field.get('table-rowcount',0))
field['table-columncount']=int(field.get('table-columncount',0))
elif role=="tableCell":
role=controlTypes.ROLE_TABLECELL
field['table-rownumber']=int(field.get('table-rownumber',0))
field['table-columnnumber']=int(field.get('table-columnnumber',0))
elif role=="footnote":
role=controlTypes.ROLE_FOOTNOTE
elif role=="endnote":
role=controlTypes.ROLE_ENDNOTE
elif role=="graphic":
role=controlTypes.ROLE_GRAPHIC
elif role=="object":
progid=field.get("progid")
if progid and progid.startswith("Equation.DSMT"):
# MathType.
role=controlTypes.ROLE_MATH
else:
role=controlTypes.ROLE_EMBEDDEDOBJECT
else:
fieldType=int(field.pop('wdFieldType',-1))
if fieldType!=-1:
role=wdFieldTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN)
if fieldType==wdFieldFormCheckBox and int(field.get('wdFieldResult','0'))>0:
field['states']=set([controlTypes.STATE_CHECKED])
elif fieldType==wdFieldFormDropDown:
field['value']=field.get('wdFieldResult',None)
fieldStatusText=field.pop('wdFieldStatusText',None)
if fieldStatusText:
field['name']=fieldStatusText
field['alwaysReportName']=True
else:
fieldType=int(field.get('wdContentControlType',-1))
if fieldType!=-1:
role=wdContentControlTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN)
if role==controlTypes.ROLE_CHECKBOX:
fieldChecked=bool(int(field.get('wdContentControlChecked','0')))
if fieldChecked:
field['states']=set([controlTypes.STATE_CHECKED])
fieldTitle=field.get('wdContentControlTitle',None)
if fieldTitle:
field['name']=fieldTitle
field['alwaysReportName']=True
if role is not None: field['role']=role
if role==controlTypes.ROLE_TABLE and field.get('longdescription'):
field['states']=set([controlTypes.STATE_HASLONGDESC])
storyType=int(field.pop('wdStoryType',0))
if storyType:
name=storyTypeLocalizedLabels.get(storyType,None)
if name:
field['name']=name
field['alwaysReportName']=True
field['role']=controlTypes.ROLE_FRAME
# Hack support for lazy fetching of row and column header text values
class ControlField(textInfos.ControlField):
def get(d,name,default=None):
if name=="table-rowheadertext":
try:
cell=self._rangeObj.cells[1]
except IndexError:
log.debugWarning("no cells for table row, possibly on end of cell mark")
return super(ControlField,d).get(name,default)
return self.obj.fetchAssociatedHeaderCellText(cell,False)
elif name=="table-columnheadertext":
try:
cell=self._rangeObj.cells[1]
except IndexError:
log.debugWarning("no cells for table row, possibly on end of cell mark")
return super(ControlField,d).get(name,default)
return self.obj.fetchAssociatedHeaderCellText(cell,True)
else:
return super(ControlField,d).get(name,default)
newField=ControlField()
newField.update(field)
return newField
def _normalizeFormatField(self,field,extraDetail=False):
_startOffset=int(field.pop('_startOffset'))
_endOffset=int(field.pop('_endOffset'))
lineSpacingRule=field.pop('wdLineSpacingRule',None)
lineSpacingVal=field.pop('wdLineSpacing',None)
if lineSpacingRule is not None:
lineSpacingRule=int(lineSpacingRule)
if lineSpacingRule==wdLineSpaceSingle:
# Translators: single line spacing
field['line-spacing']=pgettext('line spacing value',"single")
elif lineSpacingRule==wdLineSpaceDouble:
# Translators: double line spacing
field['line-spacing']=pgettext('line spacing value',"double")
elif lineSpacingRule==wdLineSpace1pt5:
# Translators: line spacing of 1.5 lines
field['line-spacing']=pgettext('line spacing value',"1.5 lines")
elif lineSpacingRule==wdLineSpaceExactly:
# Translators: exact (minimum) line spacing
field['line-spacing']=pgettext('line spacing value',"exact")
elif lineSpacingRule==wdLineSpaceAtLeast:
# Translators: line spacing of at least x point
field['line-spacing']=pgettext('line spacing value',"at least %.1f pt")%float(lineSpacingVal)
elif lineSpacingRule==wdLineSpaceMultiple:
# Translators: line spacing of x lines
field['line-spacing']=pgettext('line spacing value',"%.1f lines")%(float(lineSpacingVal)/12.0)
revisionType=int(field.pop('wdRevisionType',0))
if revisionType==wdRevisionInsert:
field['revision-insertion']=True
elif revisionType==wdRevisionDelete:
field['revision-deletion']=True
elif revisionType:
revisionLabel=wdRevisionTypeLabels.get(revisionType,None)
if revisionLabel:
field['revision']=revisionLabel
color=field.pop('color',None)
if color is not None:
field['color']=self.obj.winwordColorToNVDAColor(int(color))
try:
languageId = int(field.pop('wdLanguageId',0))
if languageId:
field['language']=self._getLanguageFromLcid(languageId)
except:
log.debugWarning("language error",exc_info=True)
pass
for x in ("first-line-indent","left-indent","right-indent","hanging-indent"):
v=field.get(x)
if not v: continue
v=float(v)
if abs(v)<0.001:
v=None
else:
v=self.obj.getLocalizedMeasurementTextForPointSize(v)
field[x]=v
return field
def _getLanguageFromLcid(self, lcid):
"""
gets a normalized locale from a lcid
"""
lang = locale.windows_locale[lcid]
if lang:
return languageHandler.normalizeLanguage(lang)
def expand(self,unit):
if unit==textInfos.UNIT_LINE:
try:
if self._rangeObj.tables.count>0 and self._rangeObj.cells.count==0:
unit=textInfos.UNIT_CHARACTER
except COMError:
pass
if unit==textInfos.UNIT_LINE:
self._expandToLineAtCaret()
elif unit==textInfos.UNIT_CHARACTER:
self._rangeObj.moveEnd(wdCharacter,1)
elif unit in NVDAUnitsToWordUnits:
self._rangeObj.Expand(NVDAUnitsToWordUnits[unit])
else:
raise NotImplementedError("unit: %s"%unit)
def compareEndPoints(self,other,which):
if which=="startToStart":
diff=self._rangeObj.Start-other._rangeObj.Start
elif which=="startToEnd":
diff=self._rangeObj.Start-other._rangeObj.End
elif which=="endToStart":
diff=self._rangeObj.End-other._rangeObj.Start
elif which=="endToEnd":
diff=self._rangeObj.End-other._rangeObj.End
else:
raise ValueError("bad argument - which: %s"%which)
if diff<0:
diff=-1
elif diff>0:
diff=1
return diff
def setEndPoint(self,other,which):
if which=="startToStart":
self._rangeObj.Start=other._rangeObj.Start
elif which=="startToEnd":
self._rangeObj.Start=other._rangeObj.End
elif which=="endToStart":
self._rangeObj.End=other._rangeObj.Start
elif which=="endToEnd":
self._rangeObj.End=other._rangeObj.End
else:
raise ValueError("bad argument - which: %s"%which)
def _get_isCollapsed(self):
if self._rangeObj.Start==self._rangeObj.End:
return True
else:
return False
def collapse(self,end=False):
if end:
oldEndOffset=self._rangeObj.end
self._rangeObj.collapse(wdCollapseEnd if end else wdCollapseStart)
if end and self._rangeObj.end<oldEndOffset:
raise RuntimeError
def copy(self):
return WordDocumentTextInfo(self.obj,None,_rangeObj=self._rangeObj)
def _get_text(self):
text=self._rangeObj.text
if not text:
text=""
return text
def _move(self,unit,direction,endPoint=None,_rangeObj=None):
if not _rangeObj:
_rangeObj=self._rangeObj
if unit in NVDAUnitsToWordUnits:
unit=NVDAUnitsToWordUnits[unit]
else:
raise NotImplementedError("unit: %s"%unit)
if endPoint=="start":
moveFunc=_rangeObj.MoveStart
elif endPoint=="end":
moveFunc=_rangeObj.MoveEnd
else:
moveFunc=_rangeObj.Move
res=moveFunc(unit,direction)
#units higher than character and word expand to contain the last text plus the insertion point offset in the document
#However move from a character before will incorrectly move to this offset which makes move/expand contridictory to each other
#Make sure that move fails if it lands on the final offset but the unit is bigger than character/word
if direction>0 and endPoint!="end" and unit not in (wdCharacter,wdWord) and (_rangeObj.start+1)==self.obj.WinwordDocumentObject.characters.count:
return 0
return res
def move(self,unit,direction,endPoint=None):
if unit!=textInfos.UNIT_LINE:
return self._move(unit,direction,endPoint)
if direction==0 or direction>1 or direction<-1:
raise NotImplementedError("moving by line is only supported collapsed and with a count of 1 or -1")
oldOffset=self._rangeObj.end if endPoint=="end" else self._rangeObj.start
newOffset=ctypes.c_long()
# Try moving by line making use of the selection temporarily
res=NVDAHelper.localLib.nvdaInProcUtils_winword_moveByLine(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,oldOffset,1 if direction<0 else 0,ctypes.byref(newOffset))
if res==0:
res=direction
newOffset=newOffset.value
if direction<0 and not endPoint and newOffset==oldOffset:
# Moving backwards by line seemed to not move.
# Therefore fallback to moving back a character, expanding to line and collapsing to start instead.
self.move(textInfos.UNIT_CHARACTER,-1)
self.expand(unit)
self.collapse()
elif direction>0 and not endPoint and newOffset<oldOffset:
# Moving forward by line seems to have wrapped back before the original position
# This can happen in some tables with merged rows.
# Try moving forward by cell, but if that fails, jump past the entire table.
res=self.move(textInfos.UNIT_CELL,direction,endPoint)
if res==0:
self.expand(textInfos.UNIT_TABLE)
self.collapse(end=True)
else:
# the move by line using the selection succeeded. Therefore update this TextInfo's position.
if not endPoint:
self._rangeObj.setRange(newOffset,newOffset)
elif endPoint=="start":
self._rangeObj.start=newOffset
elif endPoint=="end":
self._rangeObj.end=newOffset
return res
def _get_bookmark(self):
return textInfos.offsets.Offsets(self._rangeObj.Start,self._rangeObj.End)
def updateCaret(self):
self.obj.WinwordWindowObject.ScrollIntoView(self._rangeObj)
self.obj.WinwordSelectionObject.SetRange(self._rangeObj.Start,self._rangeObj.Start)
def updateSelection(self):
self.obj.WinwordWindowObject.ScrollIntoView(self._rangeObj)
self.obj.WinwordSelectionObject.SetRange(self._rangeObj.Start,self._rangeObj.End)
def getMathMl(self, field):
try:
import mathType
except:
raise LookupError("MathType not installed")
range = self._rangeObj.Duplicate
range.Start = int(field["shapeoffset"])
obj = range.InlineShapes[0].OLEFormat
try:
return mathType.getMathMl(obj)
except:
raise LookupError("Couldn't get MathML from MathType")
class WordDocumentTextInfoForTreeInterceptor(WordDocumentTextInfo):
def _get_shouldIncludeLayoutTables(self):
return config.conf['documentFormatting']['includeLayoutTables']
class BrowseModeWordDocumentTextInfo(browseMode.BrowseModeDocumentTextInfo,treeInterceptorHandler.RootProxyTextInfo):
def __init__(self,obj,position,_rangeObj=None):
if isinstance(position,WordDocument):
position=textInfos.POSITION_CARET
super(BrowseModeWordDocumentTextInfo,self).__init__(obj,position,_rangeObj=_rangeObj)
InnerTextInfoClass=WordDocumentTextInfoForTreeInterceptor
def _get_focusableNVDAObjectAtStart(self):
return self.obj.rootNVDAObject
class WordDocumentTreeInterceptor(browseMode.BrowseModeDocumentTreeInterceptor):
TextInfo=BrowseModeWordDocumentTextInfo
def _activateLongDesc(self,controlField):
longDesc=controlField.get('longdescription')
# Translators: the title of the message dialog desplaying an MS Word table description.
ui.browseableMessage(longDesc,_("Table description"))
def _get_isAlive(self):
return winUser.isWindow(self.rootNVDAObject.windowHandle)
def __contains__(self,obj):
return obj==self.rootNVDAObject
def _get_ElementsListDialog(self):
return ElementsListDialog
def _iterHeadings(self,nodeType,direction,rangeObj,includeCurrent):
neededLevel=int(nodeType[7:]) if len(nodeType)>7 else 0
isFirst=True
while True:
if not isFirst or includeCurrent:
level=rangeObj.paragraphs[1].outlineLevel
if level and 0<level<10 and (not neededLevel or neededLevel==level):
rangeObj.expand(wdParagraph)
yield WordDocumentHeadingQuickNavItem(nodeType,self,BrowseModeWordDocumentTextInfo(self,None,_rangeObj=rangeObj),level)
isFirst=False
if direction=="next":
newRangeObj=rangeObj.gotoNext(wdGoToHeading)
if not newRangeObj or newRangeObj.start<=rangeObj.start:
break
elif direction=="previous":
newRangeObj=rangeObj.gotoPrevious(wdGoToHeading)
if not newRangeObj or newRangeObj.start>=rangeObj.start:
break
rangeObj=newRangeObj
def _iterNodesByType(self,nodeType,direction="next",pos=None):
if pos:
rangeObj=pos.innerTextInfo._rangeObj
else:
rangeObj=self.rootNVDAObject.WinwordDocumentObject.range(0,0)
includeCurrent=False if pos else True
if nodeType=="link":
return LinkWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate()
elif nodeType=="annotation":
comments=CommentWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate()
revisions=RevisionWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate()
return browseMode.mergeQuickNavItemIterators([comments,revisions],direction)
elif nodeType in ("table","container"):
return TableWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate()
elif nodeType=="graphic":
return GraphicWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate()
elif nodeType.startswith('heading'):
return self._iterHeadings(nodeType,direction,rangeObj,includeCurrent)
else:
raise NotImplementedError
def _activatePosition(self, info=None):
if not info:
info=self.makeTextInfo(textInfos.POSITION_CARET)
info.activate()
def script_nextRow(self,gesture):
self.rootNVDAObject._moveInTable(row=True,forward=True)
braille.handler.handleCaretMove(self)
def script_previousRow(self,gesture):
self.rootNVDAObject._moveInTable(row=True,forward=False)
braille.handler.handleCaretMove(self)
def script_nextColumn(self,gesture):
self.rootNVDAObject._moveInTable(row=False,forward=True)
braille.handler.handleCaretMove(self)
def script_previousColumn(self,gesture):
self.rootNVDAObject._moveInTable(row=False,forward=False)
braille.handler.handleCaretMove(self)
__gestures={
"kb:tab":"trapNonCommandGesture",
"kb:shift+tab":"trapNonCommandGesture",
"kb:control+alt+upArrow": "previousRow",
"kb:control+alt+downArrow": "nextRow",
"kb:control+alt+leftArrow": "previousColumn",
"kb:control+alt+rightArrow": "nextColumn",
# We want to fall back to MS Word's real page up and page down, rather than browseMode's faked 25 lines
"kb:pageUp":None,
"kb:pageDown":None,
"kb:shift+pageUp":None,
"kb:shift+pageDown":None,
}
class WordDocument(EditableTextWithoutAutoSelectDetection, Window):
treeInterceptorClass=WordDocumentTreeInterceptor
shouldCreateTreeInterceptor=False
TextInfo=WordDocumentTextInfo
def winwordColorToNVDAColor(self,val):
if val>=0:
# normal RGB value
return colors.RGB.fromCOLORREF(val).name
elif (val&0xffffffff)==0xff000000:
# Translators: the default (automatic) color in Microsoft Word
return _("default color")
elif ((val>>28)&0xf)==0xd and ((val>>16)&0xff)==0x00:
# An MS word color index Plus intencity
# Made up of MS Word Theme Color index, hsv value ratio (MS Word darker percentage) and hsv saturation ratio (MS Word lighter percentage)
# Info: http://www.wordarticles.com/Articles/Colours/2007.php#UIConsiderations
saturationRatio=(val&0xff)/255.0
valueRatio=((val>>8)&0xff)/255.0
themeColorIndex=(val>>24)&0x0f
# Convert the MS Word theme color index to an MS Office color scheme index
schemeColorIndex=WdThemeColorIndexToMsoThemeColorSchemeIndex[themeColorIndex]
# Lookup the rgb value for the MS Office scheme color index based on the current theme
colorref=self.WinwordDocumentObject.documentTheme.themeColorScheme(schemeColorIndex).rgb
# Convert the rgb value to hsv and apply the saturation and value ratios
rgb=tuple(x/255.0 for x in colors.RGB.fromCOLORREF(colorref))
hsv=colorsys.rgb_to_hsv(*rgb)
hsv=(hsv[0],hsv[1]*saturationRatio,hsv[2]*valueRatio)
rgb=colorsys.hsv_to_rgb(*hsv)
name=colors.RGB(rgb[0]*255,rgb[1]*255,rgb[2]*255).name
return name
else:
raise ValueError("Unknown color format %x %x %x %x"%((val>>24)&0xff,(val>>16)&0xff,(val>>8)&0xff,val&0xff))
def _get_ignoreEditorRevisions(self):
try:
ignore=not self.WinwordWindowObject.view.showRevisionsAndComments
except COMError:
log.debugWarning("showRevisionsAndComments",exc_info=True)
ignore=False
self.ignoreEditorRevisions=ignore
return ignore
#: True if formatting should be ignored (text only) such as for spellCheck error field
ignoreFormatting=False
def __init__(self,*args,**kwargs):
super(WordDocument,self).__init__(*args,**kwargs)
def event_caret(self):
curSelectionPos=self.makeTextInfo(textInfos.POSITION_SELECTION)
lastSelectionPos=getattr(self,'_lastSelectionPos',None)
self._lastSelectionPos=curSelectionPos
if lastSelectionPos:
if curSelectionPos._rangeObj.isEqual(lastSelectionPos._rangeObj):
return
super(WordDocument,self).event_caret()
def _get_role(self):
return controlTypes.ROLE_EDITABLETEXT
def _get_states(self):
states=super(WordDocument,self).states
states.add(controlTypes.STATE_MULTILINE)
return states
def populateHeaderCellTrackerFromHeaderRows(self,headerCellTracker,table):
rows=table.rows
numHeaderRows=0
for rowIndex in xrange(rows.count):
try:
row=rows.item(rowIndex+1)
except COMError:
break
try:
headingFormat=row.headingFormat
except (COMError,AttributeError,NameError):
headingFormat=0
if headingFormat==-1: # is a header row
numHeaderRows+=1
else:
break
if numHeaderRows>0:
headerCellTracker.addHeaderCellInfo(rowNumber=1,columnNumber=1,rowSpan=numHeaderRows,isColumnHeader=True,isRowHeader=False)
def populateHeaderCellTrackerFromBookmarks(self,headerCellTracker,bookmarks):
for x in bookmarks:
name=x.name
lowerName=name.lower()
isColumnHeader=isRowHeader=False
if lowerName.startswith('title'):
isColumnHeader=isRowHeader=True
elif lowerName.startswith('columntitle'):
isColumnHeader=True
elif lowerName.startswith('rowtitle'):
isRowHeader=True
else:
continue
try:
headerCell=x.range.cells.item(1)
except COMError:
continue
headerCellTracker.addHeaderCellInfo(rowNumber=headerCell.rowIndex,columnNumber=headerCell.columnIndex,name=name,isColumnHeader=isColumnHeader,isRowHeader=isRowHeader)
_curHeaderCellTrackerTable=None
_curHeaderCellTracker=None
def getHeaderCellTrackerForTable(self,table):
tableRange=table.range
if not self._curHeaderCellTrackerTable or not tableRange.isEqual(self._curHeaderCellTrackerTable.range):
self._curHeaderCellTracker=HeaderCellTracker()
self.populateHeaderCellTrackerFromBookmarks(self._curHeaderCellTracker,tableRange.bookmarks)
self.populateHeaderCellTrackerFromHeaderRows(self._curHeaderCellTracker,table)
self._curHeaderCellTrackerTable=table
return self._curHeaderCellTracker
def setAsHeaderCell(self,cell,isColumnHeader=False,isRowHeader=False):
rowNumber=cell.rowIndex
columnNumber=cell.columnIndex
headerCellTracker=self.getHeaderCellTrackerForTable(cell.range.tables[1])
oldInfo=headerCellTracker.getHeaderCellInfoAt(rowNumber,columnNumber)
if oldInfo:
if isColumnHeader and not oldInfo.isColumnHeader:
oldInfo.isColumnHeader=True
elif isRowHeader and not oldInfo.isRowHeader:
oldInfo.isRowHeader=True
else:
return False
isColumnHeader=oldInfo.isColumnHeader
isRowHeader=oldInfo.isRowHeader
if isColumnHeader and isRowHeader:
name="Title_"
elif isRowHeader:
name="RowTitle_"
elif isColumnHeader:
name="ColumnTitle_"
else:
raise ValueError("One or both of isColumnHeader or isRowHeader must be True")
name+=uuid.uuid4().hex
if oldInfo:
self.WinwordDocumentObject.bookmarks[oldInfo.name].delete()
oldInfo.name=name
else:
headerCellTracker.addHeaderCellInfo(rowNumber=rowNumber,columnNumber=columnNumber,name=name,isColumnHeader=isColumnHeader,isRowHeader=isRowHeader)
self.WinwordDocumentObject.bookmarks.add(name,cell.range)
return True
def forgetHeaderCell(self,cell,isColumnHeader=False,isRowHeader=False):
rowNumber=cell.rowIndex
columnNumber=cell.columnIndex
if not isColumnHeader and not isRowHeader:
return False
headerCellTracker=self.getHeaderCellTrackerForTable(cell.range.tables[1])
info=headerCellTracker.getHeaderCellInfoAt(rowNumber,columnNumber)
if not info or not hasattr(info,'name'):
return False
if isColumnHeader and info.isColumnHeader:
info.isColumnHeader=False
elif isRowHeader and info.isRowHeader:
info.isRowHeader=False
else:
return False
headerCellTracker.removeHeaderCellInfo(info)
self.WinwordDocumentObject.bookmarks(info.name).delete()
if info.isColumnHeader or info.isRowHeader:
self.setAsHeaderCell(cell,isColumnHeader=info.isColumnHeader,isRowHeader=info.isRowHeader)
return True
def fetchAssociatedHeaderCellText(self,cell,columnHeader=False):
table=cell.range.tables[1]
rowNumber=cell.rowIndex
columnNumber=cell.columnIndex
headerCellTracker=self.getHeaderCellTrackerForTable(table)
for info in headerCellTracker.iterPossibleHeaderCellInfosFor(rowNumber,columnNumber,columnHeader=columnHeader):
textList=[]
if columnHeader:
for headerRowNumber in xrange(info.rowNumber,info.rowNumber+info.rowSpan):
tempColumnNumber=columnNumber
while tempColumnNumber>=1:
try:
headerCell=table.cell(headerRowNumber,tempColumnNumber)
except COMError:
tempColumnNumber-=1
continue
break
textList.append(headerCell.range.text)
else:
for headerColumnNumber in xrange(info.columnNumber,info.columnNumber+info.colSpan):
tempRowNumber=rowNumber
while tempRowNumber>=1:
try:
headerCell=table.cell(tempRowNumber,headerColumnNumber)
except COMError:
tempRowNumber-=1
continue
break
textList.append(headerCell.range.text)
text=" ".join(textList)
if text:
return text
def script_setColumnHeader(self,gesture):
scriptCount=scriptHandler.getLastScriptRepeatCount()
if not config.conf['documentFormatting']['reportTableHeaders']:
# Translators: a message reported in the SetColumnHeader script for Microsoft Word.
ui.message(_("Cannot set headers. Please enable reporting of table headers in Document Formatting Settings"))
return
try:
cell=self.WinwordSelectionObject.cells[1]
except COMError:
# Translators: a message when trying to perform an action on a cell when not in one in Microsoft word
ui.message(_("Not in a table cell"))
return
if scriptCount==0:
if self.setAsHeaderCell(cell,isColumnHeader=True,isRowHeader=False):
# Translators: a message reported in the SetColumnHeader script for Microsoft Word.
ui.message(_("Set row {rowNumber} column {columnNumber} as start of column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
else:
# Translators: a message reported in the SetColumnHeader script for Microsoft Word.
ui.message(_("Already set row {rowNumber} column {columnNumber} as start of column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
elif scriptCount==1:
if self.forgetHeaderCell(cell,isColumnHeader=True,isRowHeader=False):
# Translators: a message reported in the SetColumnHeader script for Microsoft Word.
ui.message(_("Removed row {rowNumber} column {columnNumber} from column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
else:
# Translators: a message reported in the SetColumnHeader script for Microsoft Word.
ui.message(_("Cannot find row {rowNumber} column {columnNumber} in column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
script_setColumnHeader.__doc__=_("Pressing once will set this cell as the first column header for any cells lower and to the right of it within this table. Pressing twice will forget the current column header for this cell.")
def script_setRowHeader(self,gesture):
scriptCount=scriptHandler.getLastScriptRepeatCount()
if not config.conf['documentFormatting']['reportTableHeaders']:
# Translators: a message reported in the SetRowHeader script for Microsoft Word.
ui.message(_("Cannot set headers. Please enable reporting of table headers in Document Formatting Settings"))
return
try:
cell=self.WinwordSelectionObject.cells[1]
except COMError:
# Translators: a message when trying to perform an action on a cell when not in one in Microsoft word
ui.message(_("Not in a table cell"))
return
if scriptCount==0:
if self.setAsHeaderCell(cell,isColumnHeader=False,isRowHeader=True):
# Translators: a message reported in the SetRowHeader script for Microsoft Word.
ui.message(_("Set row {rowNumber} column {columnNumber} as start of row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
else:
# Translators: a message reported in the SetRowHeader script for Microsoft Word.
ui.message(_("Already set row {rowNumber} column {columnNumber} as start of row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
elif scriptCount==1:
if self.forgetHeaderCell(cell,isColumnHeader=False,isRowHeader=True):
# Translators: a message reported in the SetRowHeader script for Microsoft Word.
ui.message(_("Removed row {rowNumber} column {columnNumber} from row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
else:
# Translators: a message reported in the SetRowHeader script for Microsoft Word.
ui.message(_("Cannot find row {rowNumber} column {columnNumber} in row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex))
script_setRowHeader.__doc__=_("Pressing once will set this cell as the first row header for any cells lower and to the right of it within this table. Pressing twice will forget the current row header for this cell.")
def script_reportCurrentHeaders(self,gesture):
cell=self.WinwordSelectionObject.cells[1]
rowText=self.fetchAssociatedHeaderCellText(cell,False)
columnText=self.fetchAssociatedHeaderCellText(cell,True)
ui.message("Row %s, column %s"%(rowText or "empty",columnText or "empty"))
def _get_WinwordVersion(self):
if not hasattr(self,'_WinwordVersion'):
self._WinwordVersion=float(self.WinwordApplicationObject.version)
return self._WinwordVersion
def _get_documentWindowHandle(self):
return self.windowHandle
def _get_WinwordWindowObject(self):
if not getattr(self,'_WinwordWindowObject',None):
try:
pDispatch=oleacc.AccessibleObjectFromWindow(self.documentWindowHandle,winUser.OBJID_NATIVEOM,interface=comtypes.automation.IDispatch)
except (COMError, WindowsError):
log.debugWarning("Could not get MS Word object model from window %s with class %s"%(self.documentWindowHandle,winUser.getClassName(self.documentWindowHandle)),exc_info=True)
return None
self._WinwordWindowObject=comtypes.client.dynamic.Dispatch(pDispatch)
return self._WinwordWindowObject
def _get_WinwordDocumentObject(self):
if not getattr(self,'_WinwordDocumentObject',None):
windowObject=self.WinwordWindowObject
if not windowObject: return None
self._WinwordDocumentObject=windowObject.document
return self._WinwordDocumentObject
def _get_WinwordApplicationObject(self):
if not getattr(self,'_WinwordApplicationObject',None):
self._WinwordApplicationObject=self.WinwordWindowObject.application
return self._WinwordApplicationObject
def _get_WinwordSelectionObject(self):
if not getattr(self,'_WinwordSelectionObject',None):
windowObject=self.WinwordWindowObject
if not windowObject: return None
self._WinwordSelectionObject=windowObject.selection
return self._WinwordSelectionObject
def _WaitForValueChangeForAction(self,action,fetcher,timeout=0.15):
oldVal=fetcher()
action()
startTime=curTime=time.time()
curVal=fetcher()
while curVal==oldVal and (curTime-startTime)<timeout:
time.sleep(0.01)
curVal=fetcher()
curTime=time.time()
return curVal
def script_toggleBold(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.font.bold)
if val:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Bold on"))
else:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Bold off"))
def script_toggleItalic(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.font.italic)
if val:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Italic on"))
else:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Italic off"))
def script_toggleUnderline(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.font.underline)
if val:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Underline on"))
else:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Underline off"))
def script_toggleAlignment(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.paragraphFormat.alignment)
alignmentMessages={
# Translators: a an alignment in Microsoft Word
wdAlignParagraphLeft:_("Left aligned"),
# Translators: a an alignment in Microsoft Word
wdAlignParagraphCenter:_("centered"),
# Translators: a an alignment in Microsoft Word
wdAlignParagraphRight:_("Right aligned"),
# Translators: a an alignment in Microsoft Word
wdAlignParagraphJustify:_("Justified"),
}
msg=alignmentMessages.get(val)
if msg:
ui.message(msg)
def script_toggleSuperscriptSubscript(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: (self.WinwordSelectionObject.font.superscript,self.WinwordSelectionObject.font.subscript))
if val[0]:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Superscript"))
elif val[1]:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Subscript"))
else:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Baseline"))
def script_moveParagraphDown(self,gesture):
oldBookmark=self.makeTextInfo(textInfos.POSITION_CARET).bookmark
gesture.send()
if self._hasCaretMoved(oldBookmark)[0]:
info=self.makeTextInfo(textInfos.POSITION_SELECTION)
info.collapse()
info.move(textInfos.UNIT_PARAGRAPH,-1,endPoint="start")
lastParaText=info.text.strip()
if lastParaText:
# Translators: a message reported when a paragraph is moved below another paragraph
ui.message(_("Moved below %s")%lastParaText)
else:
# Translators: a message reported when a paragraph is moved below a blank paragraph
ui.message(_("Moved below blank paragraph"))
def script_moveParagraphUp(self,gesture):
oldBookmark=self.makeTextInfo(textInfos.POSITION_CARET).bookmark
gesture.send()
if self._hasCaretMoved(oldBookmark)[0]:
info=self.makeTextInfo(textInfos.POSITION_SELECTION)
info.collapse()
info.move(textInfos.UNIT_PARAGRAPH,1)
info.expand(textInfos.UNIT_PARAGRAPH)
lastParaText=info.text.strip()
if lastParaText:
# Translators: a message reported when a paragraph is moved above another paragraph
ui.message(_("Moved above %s")%lastParaText)
else:
# Translators: a message reported when a paragraph is moved above a blank paragraph
ui.message(_("Moved above blank paragraph"))
def script_increaseDecreaseOutlineLevel(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.paragraphFormat.outlineLevel)
style=self.WinwordSelectionObject.style.nameLocal
# Translators: the message when the outline level / style is changed in Microsoft word
ui.message(_("{styleName} style, outline level {outlineLevel}").format(styleName=style,outlineLevel=val))
def script_increaseDecreaseFontSize(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda: self.WinwordSelectionObject.font.size)
# Translators: a message when increasing or decreasing font size in Microsoft Word
ui.message(_("{size:g} point font").format(size=val))
def script_caret_moveByCell(self,gesture):
gesture.send()
info=self.makeTextInfo(textInfos.POSITION_SELECTION)
inTable=info._rangeObj.tables.count>0
isCollapsed=info.isCollapsed
if inTable:
info.expand(textInfos.UNIT_CELL)
speech.speakTextInfo(info,reason=controlTypes.REASON_FOCUS)
braille.handler.handleCaretMove(self)
def script_tab(self,gesture):
gesture.send()
info=self.makeTextInfo(textInfos.POSITION_SELECTION)
inTable=info._rangeObj.tables.count>0
isCollapsed=info.isCollapsed
if inTable and isCollapsed:
info.expand(textInfos.UNIT_CELL)
isCollapsed=False
if not isCollapsed:
speech.speakTextInfo(info,reason=controlTypes.REASON_FOCUS)
braille.handler.handleCaretMove(self)
if isCollapsed:
offset=info._rangeObj.information(wdHorizontalPositionRelativeToPage)
msg=self.getLocalizedMeasurementTextForPointSize(offset)
ui.message(msg)
if info._rangeObj.paragraphs[1].range.start==info._rangeObj.start:
info.expand(textInfos.UNIT_LINE)
speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET)
def getLocalizedMeasurementTextForPointSize(self,offset):
options=self.WinwordApplicationObject.options
useCharacterUnit=options.useCharacterUnit
if useCharacterUnit:
offset=offset/self.WinwordSelectionObject.font.size
# Translators: a measurement in Microsoft Word
return _("{offset:.3g} characters").format(offset=offset)
else:
unit=options.measurementUnit
if unit==wdInches:
offset=offset/72.0
# Translators: a measurement in Microsoft Word
return _("{offset:.3g} inches").format(offset=offset)
elif unit==wdCentimeters:
offset=offset/28.35
# Translators: a measurement in Microsoft Word
return _("{offset:.3g} centimeters").format(offset=offset)
elif unit==wdMillimeters:
offset=offset/2.835
# Translators: a measurement in Microsoft Word
return _("{offset:.3g} millimeters").format(offset=offset)
elif unit==wdPoints:
# Translators: a measurement in Microsoft Word
return _("{offset:.3g} points").format(offset=offset)
elif unit==wdPicas:
offset=offset/12.0
# Translators: a measurement in Microsoft Word
# See http://support.microsoft.com/kb/76388 for details.
return _("{offset:.3g} picas").format(offset=offset)
def script_reportCurrentComment(self,gesture):
info=self.makeTextInfo(textInfos.POSITION_CARET)
info.expand(textInfos.UNIT_CHARACTER)
fields=info.getTextWithFields(formatConfig={'reportComments':True})
for field in reversed(fields):
if isinstance(field,textInfos.FieldCommand) and isinstance(field.field,textInfos.FormatField):
commentReference=field.field.get('comment')
if commentReference:
offset=int(commentReference)
range=self.WinwordDocumentObject.range(offset,offset+1)
try:
text=range.comments[1].range.text
except COMError:
break
if text:
ui.message(text)
return
# Translators: a message when there is no comment to report in Microsoft Word
ui.message(_("No comments"))
# Translators: a description for a script
script_reportCurrentComment.__doc__=_("Reports the text of the comment where the System caret is located.")
def script_changeLineSpacing(self,gesture):
val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda:self.WinwordSelectionObject.ParagraphFormat.LineSpacingRule)
if val == wdLineSpaceSingle:
# Translators: a message when switching to single line spacing in Microsoft word
ui.message(_("Single line spacing"))
elif val == wdLineSpaceDouble:
# Translators: a message when switching to double line spacing in Microsoft word
ui.message(_("Double line spacing"))
elif val == wdLineSpace1pt5:
# Translators: a message when switching to 1.5 line spaceing in Microsoft word
ui.message(_("1.5 line spacing"))
def _moveInTable(self,row=True,forward=True):
info=self.makeTextInfo(textInfos.POSITION_CARET)
info.expand(textInfos.UNIT_CHARACTER)
formatConfig=config.conf['documentFormatting'].copy()
formatConfig['reportTables']=True
commandList=info.getTextWithFields(formatConfig)
if len(commandList)<3 or commandList[1].field.get('role',None)!=controlTypes.ROLE_TABLE or commandList[2].field.get('role',None)!=controlTypes.ROLE_TABLECELL:
# Translators: The message reported when a user attempts to use a table movement command
# when the cursor is not withnin a table.
ui.message(_("Not in table"))
return False
rowCount=commandList[1].field.get('table-rowcount',1)
columnCount=commandList[1].field.get('table-columncount',1)
rowNumber=commandList[2].field.get('table-rownumber',1)
columnNumber=commandList[2].field.get('table-columnnumber',1)
try:
table=info._rangeObj.tables[1]
except COMError:
log.debugWarning("Could not get MS Word table object indicated in XML")
ui.message(_("Not in table"))
return False
_cell=table.cell
getCell=lambda thisIndex,otherIndex: _cell(thisIndex,otherIndex) if row else _cell(otherIndex,thisIndex)
thisIndex=rowNumber if row else columnNumber
otherIndex=columnNumber if row else rowNumber
thisLimit=(rowCount if row else columnCount) if forward else 1
limitOp=operator.le if forward else operator.ge
incdecFunc=operator.add if forward else operator.sub
foundCell=None
curOtherIndex=otherIndex
while curOtherIndex>0:
curThisIndex=incdecFunc(thisIndex,1)
while limitOp(curThisIndex,thisLimit):
try:
foundCell=getCell(curThisIndex,curOtherIndex).range
except COMError:
pass
if foundCell: break
curThisIndex=incdecFunc(curThisIndex,1)
if foundCell: break
curOtherIndex-=1
if not foundCell:
ui.message(_("Edge of table"))
return False
newInfo=WordDocumentTextInfo(self,textInfos.POSITION_CARET,_rangeObj=foundCell)
speech.speakTextInfo(newInfo,reason=controlTypes.REASON_CARET)
newInfo.collapse()
newInfo.updateCaret()
return True
def script_nextRow(self,gesture):
self._moveInTable(row=True,forward=True)
def script_previousRow(self,gesture):
self._moveInTable(row=True,forward=False)
def script_nextColumn(self,gesture):
self._moveInTable(row=False,forward=True)
def script_previousColumn(self,gesture):
self._moveInTable(row=False,forward=False)
def script_nextParagraph(self,gesture):
info=self.makeTextInfo(textInfos.POSITION_CARET)
# #4375: can't use self.move here as it may check document.chracters.count which can take for ever on large documents.
info._rangeObj.move(wdParagraph,1)
info.updateCaret()
self._caretScriptPostMovedHelper(textInfos.UNIT_PARAGRAPH,gesture,None)
script_nextParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET
def script_previousParagraph(self,gesture):
info=self.makeTextInfo(textInfos.POSITION_CARET)
# #4375: keeping cemetrical with nextParagraph script.
info._rangeObj.move(wdParagraph,-1)
info.updateCaret()
self._caretScriptPostMovedHelper(textInfos.UNIT_PARAGRAPH,gesture,None)
script_previousParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET
__gestures = {
"kb:control+[":"increaseDecreaseFontSize",
"kb:control+]":"increaseDecreaseFontSize",
"kb:control+shift+,":"increaseDecreaseFontSize",
"kb:control+shift+.":"increaseDecreaseFontSize",
"kb:control+b":"toggleBold",
"kb:control+i":"toggleItalic",
"kb:control+u":"toggleUnderline",
"kb:control+=":"toggleSuperscriptSubscript",
"kb:control+shift+=":"toggleSuperscriptSubscript",
"kb:control+l":"toggleAlignment",
"kb:control+e":"toggleAlignment",
"kb:control+r":"toggleAlignment",
"kb:control+j":"toggleAlignment",
"kb:alt+shift+downArrow":"moveParagraphDown",
"kb:alt+shift+upArrow":"moveParagraphUp",
"kb:alt+shift+rightArrow":"increaseDecreaseOutlineLevel",
"kb:alt+shift+leftArrow":"increaseDecreaseOutlineLevel",
"kb:control+shift+n":"increaseDecreaseOutlineLevel",
"kb:control+alt+1":"increaseDecreaseOutlineLevel",
"kb:control+alt+2":"increaseDecreaseOutlineLevel",
"kb:control+alt+3":"increaseDecreaseOutlineLevel",
"kb:control+1":"changeLineSpacing",
"kb:control+2":"changeLineSpacing",
"kb:control+5":"changeLineSpacing",
"kb:tab": "tab",
"kb:shift+tab": "tab",
"kb:NVDA+shift+c":"setColumnHeader",
"kb:NVDA+shift+r":"setRowHeader",
"kb:NVDA+shift+h":"reportCurrentHeaders",
"kb:control+alt+upArrow": "previousRow",
"kb:control+alt+downArrow": "nextRow",
"kb:control+alt+leftArrow": "previousColumn",
"kb:control+alt+rightArrow": "nextColumn",
"kb:control+downArrow":"nextParagraph",
"kb:control+upArrow":"previousParagraph",
"kb:alt+home":"caret_moveByCell",
"kb:alt+end":"caret_moveByCell",
"kb:alt+pageUp":"caret_moveByCell",
"kb:alt+pageDown":"caret_moveByCell",
"kb:alt+shift+home":"caret_changeSelection",
"kb:alt+shift+end":"caret_changeSelection",
"kb:alt+shift+pageUp":"caret_changeSelection",
"kb:alt+shift+pageDown":"caret_changeSelection",
"kb:control+pageUp": "caret_moveByLine",
"kb:control+pageDown": "caret_moveByLine",
"kb:NVDA+alt+c":"reportCurrentComment",
}
class WordDocument_WwN(WordDocument):
def _get_documentWindowHandle(self):
w=NVDAHelper.localLib.findWindowWithClassInThread(self.windowThreadID,u"_WwG",True)
if not w:
log.debugWarning("Could not find window for class _WwG in thread.")
w=super(WordDocument_WwN,self).documentWindowHandle
return w
def _get_WinwordWindowObject(self):
window=super(WordDocument_WwN,self).WinwordWindowObject
if not window: return None
try:
return window.application.activeWindow.activePane
except COMError:
log.debugWarning("Unable to get activePane")
return window.application.windows[1].activePane
__gestures={
"kb:tab":None,
"kb:shift+tab":None,
}
class ElementsListDialog(browseMode.ElementsListDialog):
ELEMENT_TYPES=(browseMode.ElementsListDialog.ELEMENT_TYPES[0],browseMode.ElementsListDialog.ELEMENT_TYPES[1],
# Translators: The label of a radio button to select the type of element
# in the browse mode Elements List dialog.
("annotation", _("&Annotations")),
)