powerpnt.py
46.4 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
#appModules/powerpnt.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2012-2015 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import comtypes
from comtypes.automation import IDispatch
import comtypes.client
import ctypes
import oleacc
import comHelper
import ui
import queueHandler
import colors
import api
import speech
import sayAllHandler
import NVDAHelper
import winUser
from treeInterceptorHandler import DocumentTreeInterceptor
from NVDAObjects import NVDAObjectTextInfo
from displayModel import DisplayModelTextInfo, EditableTextDisplayModelTextInfo
import textInfos.offsets
import eventHandler
import appModuleHandler
from NVDAObjects.IAccessible import IAccessible, getNVDAObjectFromEvent
from NVDAObjects.window import Window
from NVDAObjects.behaviors import EditableTextWithoutAutoSelectDetection, EditableText
import braille
from cursorManager import ReviewCursorManager
import controlTypes
from logHandler import log
import scriptHandler
# Window classes where PowerPoint's object model should be used
# These also all request to have their (incomplete) UI Automation implementations disabled. [MS Office 2013]
objectModelWindowClasses=set(["paneClassDC","mdiClass","screenClass"])
MATHTYPE_PROGID = "Equation.DSMT"
#comtypes COM interface definition for Powerpoint application object's events
class EApplication(IDispatch):
_iid_=comtypes.GUID('{914934C2-5A91-11CF-8700-00AA0060263B}')
_methods_=[]
_disp_methods_=[
comtypes.DISPMETHOD([comtypes.dispid(2001)],None,"WindowSelectionChange",
(['in'],ctypes.POINTER(IDispatch),'sel'),),
comtypes.DISPMETHOD([comtypes.dispid(2013)],None,"SlideShowNextSlide",
(['in'],ctypes.POINTER(IDispatch),'slideShowWindow'),),
]
#Our implementation of the EApplication COM interface to receive application events
class ppEApplicationSink(comtypes.COMObject):
_com_interfaces_=[EApplication,IDispatch]
def SlideShowNextSlide(self,slideShowWindow=None):
i=winUser.getGUIThreadInfo(0)
oldFocus=api.getFocusObject()
if not isinstance(oldFocus,SlideShowWindow) or i.hwndFocus!=oldFocus.windowHandle:
return
oldFocus.treeInterceptor.rootNVDAObject.handleSlideChange()
def WindowSelectionChange(self,sel):
i=winUser.getGUIThreadInfo(0)
oldFocus=api.getFocusObject()
if not isinstance(oldFocus,Window) or i.hwndFocus!=oldFocus.windowHandle:
return
if isinstance(oldFocus,DocumentWindow):
documentWindow=oldFocus
elif isinstance(oldFocus,PpObject):
documentWindow=oldFocus.documentWindow
else:
return
documentWindow.ppSelection=sel
documentWindow.handleSelectionChange()
#Bullet types
ppBulletNumbered=2
# values for enumeration 'PpPlaceholderType'
ppPlaceholderMixed = -2
ppPlaceholderTitle = 1
ppPlaceholderBody = 2
ppPlaceholderCenterTitle = 3
ppPlaceholderSubtitle = 4
ppPlaceholderVerticalTitle = 5
ppPlaceholderVerticalBody = 6
ppPlaceholderObject = 7
ppPlaceholderChart = 8
ppPlaceholderBitmap = 9
ppPlaceholderMediaClip = 10
ppPlaceholderOrgChart = 11
ppPlaceholderTable = 12
ppPlaceholderSlideNumber = 13
ppPlaceholderHeader = 14
ppPlaceholderFooter = 15
ppPlaceholderDate = 16
ppPlaceholderVerticalObject = 17
ppPlaceholderPicture = 18
ppPlaceholderLabels={
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderTitle:_("Title placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderBody:_("Text placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderCenterTitle:_("Center Title placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderSubtitle:_("Subtitle placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderVerticalTitle:_("Vertical Title placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderVerticalBody:_("Vertical Text placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderObject:_("Object placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderChart:_("Chart placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderBitmap:_("Bitmap placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderMediaClip:_("Media Clip placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderOrgChart:_("Org Chart placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderTable:_("Table placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderSlideNumber:_("Slide Number placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderHeader:_("Header placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderFooter:_("Footer placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderDate:_("Date placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderVerticalObject:_("Vertical Object placeholder"),
# Translators: Describes a type of placeholder shape in Microsoft PowerPoint.
ppPlaceholderPicture:_("Picture placeholder"),
}
#selection types
ppSelectionNone=0
ppSelectionSlides=1
ppSelectionShapes=2
ppSelectionText=3
# values for enumeration 'PpViewType'
ppViewSlide = 1
ppViewSlideMaster = 2
ppViewNotesPage = 3
ppViewHandoutMaster = 4
ppViewNotesMaster = 5
ppViewOutline = 6
ppViewSlideSorter = 7
ppViewTitleMaster = 8
ppViewNormal = 9
ppViewPrintPreview = 10
ppViewThumbnails = 11
ppViewMasterThumbnails = 12
ppViewTypeLabels={
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewSlide:_("Slide view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewSlideMaster:_("Slide Master view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewNotesPage:_("Notes page"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewHandoutMaster:_("Handout Master view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewNotesMaster:_("Notes Master view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewOutline:_("Outline view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewSlideSorter:_("Slide Sorter view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewTitleMaster:_("Title Master view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewNormal:_("Normal view"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewPrintPreview:_("Print Preview"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewThumbnails:_("Thumbnails"),
# Translators: a label for a particular view or pane in Microsoft PowerPoint
ppViewMasterThumbnails:_("Master Thumbnails"),
}
# values for enumeration 'MsoShapeType'
msoShapeTypeMixed = -2
msoAutoShape = 1
msoCallout = 2
msoChart = 3
msoComment = 4
msoFreeform = 5
msoGroup = 6
msoEmbeddedOLEObject = 7
msoFormControl = 8
msoLine = 9
msoLinkedOLEObject = 10
msoLinkedPicture = 11
msoOLEControlObject = 12
msoPicture = 13
msoPlaceholder = 14
msoTextEffect = 15
msoMedia = 16
msoTextBox = 17
msoScriptAnchor = 18
msoTable = 19
msoCanvas = 20
msoDiagram = 21
msoInk = 22
msoInkComment = 23
msoSmartArt = 24
msoShapeTypesToNVDARoles={
msoChart:controlTypes.ROLE_CHART,
msoGroup:controlTypes.ROLE_GROUPING,
msoEmbeddedOLEObject:controlTypes.ROLE_EMBEDDEDOBJECT,
msoLine:controlTypes.ROLE_LINE,
msoLinkedOLEObject:controlTypes.ROLE_EMBEDDEDOBJECT,
msoLinkedPicture:controlTypes.ROLE_GRAPHIC,
msoPicture:controlTypes.ROLE_GRAPHIC,
msoTextBox:controlTypes.ROLE_TEXTFRAME,
msoTable:controlTypes.ROLE_TABLE,
msoCanvas:controlTypes.ROLE_CANVAS,
msoDiagram:controlTypes.ROLE_DIAGRAM,
}
# PpMouseActivation
ppMouseClick=1
# PpActionType
ppActionHyperlink=7
def getBulletText(ppBulletFormat):
t=ppBulletFormat.type
if t==ppBulletNumbered:
return "%d."%ppBulletFormat.number #(ppBulletFormat.startValue+(ppBulletFormat.number-1))
elif t:
return unichr(ppBulletFormat.character)
def walkPpShapeRange(ppShapeRange):
for ppShape in ppShapeRange:
if ppShape.type==msoGroup:
for ppChildShape in walkPpShapeRange(ppShape.groupItems):
yield ppChildShape
else:
yield ppShape
class PaneClassDC(Window):
"""Handles fetching of the Powerpoint object model."""
presentationType=Window.presType_content
role=controlTypes.ROLE_PANE
value=None
TextInfo=DisplayModelTextInfo
_cache_currentSlide=False
def _get_currentSlide(self):
try:
ppSlide=self.ppObjectModel.view.slide
except comtypes.COMError:
return None
self.currentSlide=SlideBase(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppSlide)
return self.currentSlide
def _get_ppVersionMajor(self):
self.ppVersionMajor=int(self.ppObjectModel.application.version.split('.')[0])
return self.ppVersionMajor
class DocumentWindow(PaneClassDC):
"""Represents the document window for a presentation. Bounces focus to the currently selected slide, shape or text frame."""
def _get_ppDocumentViewType(self):
try:
viewType=self.ppObjectModel.viewType
except comtypes.COMError:
return None
self.ppDocumentViewType=viewType
return self.ppDocumentViewType
def _get_ppActivePaneViewType(self):
try:
viewType=self.ppObjectModel.activePane.viewType
except comtypes.COMError:
return None
self.ppActivePaneViewType=viewType
return self.ppActivePaneViewType
def _isEqual(self,other):
return self.windowHandle==other.windowHandle and self.name==other.name
def _get_name(self):
label=ppViewTypeLabels.get(self.ppActivePaneViewType)
if not label:
return super(PaneClassDC,self).name
slide=self.currentSlide
if slide:
label=" - ".join([slide.name,label])
return label
def _get_currentSlide(self):
if self.ppActivePaneViewType in (ppViewSlideSorter,ppViewThumbnails,ppViewMasterThumbnails): return None
return super(DocumentWindow,self).currentSlide
def _get_ppSelection(self):
"""Fetches and caches the current Powerpoint Selection object for the current presentation."""
self.ppSelection=self.ppObjectModel.selection
return self.ppSelection
def _get_selection(self):
"""Fetches an NVDAObject representing the current presentation's selected slide, shape or text frame."""
sel=self.ppSelection
selType=sel.type
#MS Powerpoint 2007 and below does not correctly indecate text selection in the notes page when in normal view
if selType==0 and self.ppVersionMajor<=12:
if self.ppActivePaneViewType==ppViewNotesPage and self.ppDocumentViewType==ppViewNormal:
selType=ppSelectionText
if selType==ppSelectionShapes: #Shape
#The selected shape could be within a group shape
if sel.hasChildShapeRange:
ppObj=sel.childShapeRange[1]
else: #a normal top level shape
ppObj=sel.shapeRange[1]
#Specifically handle shapes representing a table as they have row and column counts etc
if ppObj.hasTable:
return Table(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
else: #Generic shape
return Shape(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
elif selType==ppSelectionText: #Text frame
#TextRange objects in Powerpoint do not allow moving/expanding.
#Therefore A full TextRange object must be fetched from the original TextFrame the selection is in.
#MS Powerpoint 2003 also throws COMErrors when there is no TextRange.parent
try:
ppObj=sel.textRange.parent
except comtypes.COMError:
ppObj=None
if ppObj:
return TextFrame(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
#For TextRange objects for TextFrame objects in table cells and the notes page, TextRange object's parent does not work!
#For tables: Get the shape the selection is in -- should be the table.
try:
shape=sel.shapeRange[1]
except comtypes.COMError:
shape=None
if shape and shape.hasTable:
#Only way to find the selected cell in a table is to... walk through it.
selectedTableCell=None
colNum=0
for col in shape.table.columns:
colNum+=1
rowNum=0
for tableCell in col.cells:
rowNum+=1
if tableCell.selected:
selectedTableCell=tableCell
break
if selectedTableCell:
break
if selectedTableCell:
#We found the selected table cell
#The TextFrame we want is within the shape in this table cell.
#However, the shape in the table cell seems to always be rather broken -- hardly any properties work.
#Therefore, Explicitly set the table cell as the TextFrame object's parent, skipping the broken shape
ppObj=selectedTableCell.shape.textFrame
obj=TableCellTextFrame(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
table=Table(windowHandle=self.windowHandle,documentWindow=self,ppObject=shape)
obj.parent=TableCell(windowHandle=self.windowHandle,documentWindow=self,ppObject=selectedTableCell,table=table,rowNumber=rowNum,columnNumber=colNum)
return obj
#TextRange object did not have a parent, and we're not in a table.
if self.ppActivePaneViewType==ppViewNotesPage and self.ppDocumentViewType==ppViewNormal:
#We're in the notes page in normal view
#The TextFrame in this case will be located in the notes page's second shape.
slide=sel.slideRange[1]
notesPage=slide.notesPage[1]
shape=notesPage.shapes[2]
ppObj=shape.textFrame
return NotesTextFrame(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
if ppObj:
return TextFrame(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
if selType==ppSelectionSlides:
try:
ppObj=sel.slideRange[1]
except comtypes.COMError:
#Master thumbnails sets the selected slide as view.slide but not selection.slideRange
try:
ppObj=self.ppObjectModel.view.slide
except comtypes.COMError:
ppObj=None
if ppObj:
return SlideBase(windowHandle=self.windowHandle,documentWindow=self,ppObject=ppObj)
def _get_focusRedirect(self):
return self.selection
def _get_firstChild(self):
return self.selection
def handleSelectionChange(self):
"""Pushes focus to the newly selected object."""
if getattr(self,"_isHandlingSelectionChange",False):
# #3394: A COM event can cause this function to run within itself.
# This can cause double speaking, so stop here if we're already running.
return
self._isHandlingSelectionChange=True
try:
obj=self.selection
if not obj:
obj=IAccessible(windowHandle=self.windowHandle,IAccessibleObject=self.IAccessibleObject,IAccessibleChildID=self.IAccessibleChildID)
if obj and obj!=eventHandler.lastQueuedFocusObject:
eventHandler.queueEvent("gainFocus",obj)
finally:
self._isHandlingSelectionChange=False
def event_gainFocus(self):
"""Bounces focus to the currently selected slide, shape or Text frame."""
obj=self.selection
if obj:
eventHandler.queueEvent("focusEntered",self)
eventHandler.queueEvent("gainFocus",obj)
else:
super(DocumentWindow,self).event_gainFocus()
def script_selectionChange(self,gesture):
gesture.send()
if scriptHandler.isScriptWaiting():
return
self.handleSelectionChange()
script_selectionChange.canPropagate=True
__gestures={k:"selectionChange" for k in (
"kb:tab","kb:shift+tab",
"kb:leftArrow","kb:rightArrow","kb:upArrow","kb:downArrow",
"kb:shift+leftArrow","kb:shift+rightArrow","kb:shift+upArrow","kb:shift+downArrow",
"kb:pageUp","kb:pageDown",
"kb:home","kb:control+home","kb:end","kb:control+end",
"kb:shift+home","kb:shift+control+home","kb:shift+end","kb:shift+control+end",
"kb:delete","kb:backspace",
)}
class OutlinePane(EditableTextWithoutAutoSelectDetection,PaneClassDC):
TextInfo=EditableTextDisplayModelTextInfo
role=controlTypes.ROLE_EDITABLETEXT
class PpObject(Window):
"""
The base NVDAObject for slides, shapes and text frames.
Accepts and holds references to the original Document window, and the current object's Powerpoint object.
Also has some utility functions and scripts for managing selection changes.
Note No events are used to detect selection changes, its all keyboard commands for now.
"""
next=None
previous=None
def __init__(self,windowHandle=None,documentWindow=None,ppObject=None):
self.documentWindow=documentWindow
self.ppObject=ppObject
super(PpObject,self).__init__(windowHandle=windowHandle)
def _get_parent(self):
return self.documentWindow
def script_selectionChange(self,gesture):
return self.documentWindow.script_selectionChange(gesture)
__gestures={
"kb:escape":"selectionChange",
}
class SlideBase(PpObject):
presentationType=Window.presType_content
def findOverlayClasses(self,clsList):
if isinstance(self.documentWindow,DocumentWindow) and self.documentWindow.ppActivePaneViewType in (ppViewSlideMaster,ppViewTitleMaster,ppViewNotesMaster,ppViewHandoutMaster,ppViewMasterThumbnails):
clsList.append(Master)
else:
clsList.append(Slide)
clsList.append(SlideBase)
def _isEqual(self,other):
return super(SlideBase,self)._isEqual(other) and self.name==other.name
role=controlTypes.ROLE_PANE
class Slide(SlideBase):
"""Represents a single slide in Powerpoint."""
def _get_name(self):
try:
title=self.ppObject.shapes.title.textFrame.textRange.text
except comtypes.COMError:
title=None
try:
number=self.ppObject.slideNumber
except comtypes.COMError:
number=""
# Translators: the label for a slide in Microsoft PowerPoint.
name=_("Slide {slideNumber}").format(slideNumber=number)
if title:
name+=" (%s)"%title
return name
def _get_positionInfo(self):
slideNumber=self.ppObject.slideNumber
numSlides=self.ppObject.parent.slides.count
return {'indexInGroup':slideNumber,'similarItemsInGroup':numSlides}
class Master(SlideBase):
def _get_name(self):
return self.ppObject.name
class Shape(PpObject):
"""Represents a single shape (rectangle, group, picture, Text bos etc in Powerpoint."""
presentationType=Window.presType_content
def __init__(self, **kwargs):
super(Shape, self).__init__(**kwargs)
if self.role == controlTypes.ROLE_EMBEDDEDOBJECT:
if self.ppObject.OLEFormat.ProgID.startswith(MATHTYPE_PROGID):
self.role = controlTypes.ROLE_MATH
def _get__overlapInfo(self):
slideWidth=self.appModule._ppApplication.activePresentation.pageSetup.slideWidth
slideHeight=self.appModule._ppApplication.activePresentation.pageSetup.slideHeight
left=self.ppObject.left
top=self.ppObject.top
right=left+self.ppObject.width
bottom=top+self.ppObject.height
name=self.ppObject.name
slideShapeRange=self.documentWindow.currentSlide.ppObject.shapes.range()
otherIsBehind=True
infrontInfo=None
behindInfo=None
for ppShape in walkPpShapeRange(slideShapeRange):
otherName=ppShape.name
if otherName==name:
otherIsBehind=False
continue
otherLeft=ppShape.left
otherTop=ppShape.top
otherRight=otherLeft+ppShape.width
otherBottom=otherTop+ppShape.height
if otherLeft>=right or otherRight<=left:
continue
if otherTop>=bottom or otherBottom<=top:
continue
info={}
info['label']=Shape(windowHandle=self.windowHandle,documentWindow=self.documentWindow,ppObject=ppShape).name
info['otherIsBehind']=otherIsBehind
info['overlapsOtherLeftBy']=right-otherLeft if right<otherRight else 0
info['overlapsOtherTopBy']=bottom-otherTop if bottom<otherBottom else 0
info['overlapsOtherRightBy']=otherRight-left if otherLeft<left else 0
info['overlapsOtherBottomBy']=otherBottom-top if otherTop<top else 0
if otherIsBehind:
behindInfo=info
else:
infrontInfo=info
break
self._overlapInfo=behindInfo,infrontInfo
return self._overlapInfo
def _getOverlapText(self):
textList=[]
for otherInfo in self._overlapInfo:
if otherInfo is None:
continue
otherIsBehind=otherInfo['otherIsBehind']
otherLabel=otherInfo['label']
total=True
overlapsOtherLeftBy=otherInfo['overlapsOtherLeftBy']
if overlapsOtherLeftBy>0:
total=False
if otherIsBehind:
# Translators: A message when a shape is infront of another shape on a Powerpoint slide
textList.append(_("covers left of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherLeftBy))
else:
# Translators: A message when a shape is behind another shape on a powerpoint slide
textList.append(_("behind left of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherLeftBy))
overlapsOtherTopBy=otherInfo['overlapsOtherTopBy']
if overlapsOtherTopBy>0:
total=False
if otherIsBehind:
# Translators: A message when a shape is infront of another shape on a Powerpoint slide
textList.append(_("covers top of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherTopBy))
else:
# Translators: A message when a shape is behind another shape on a powerpoint slide
textList.append(_("behind top of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherTopBy))
overlapsOtherRightBy=otherInfo['overlapsOtherRightBy']
if overlapsOtherRightBy>0:
total=False
if otherIsBehind:
# Translators: A message when a shape is infront of another shape on a Powerpoint slide
textList.append(_("covers right of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherRightBy))
else:
# Translators: A message when a shape is behind another shape on a powerpoint slide
textList.append(_("behind right of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherRightBy))
overlapsOtherBottomBy=otherInfo['overlapsOtherBottomBy']
if overlapsOtherBottomBy>0:
total=False
if otherIsBehind:
# Translators: A message when a shape is infront of another shape on a Powerpoint slide
textList.append(_("covers bottom of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherBottomBy))
else:
# Translators: A message when a shape is behind another shape on a powerpoint slide
textList.append(_("behind bottom of {otherShape} by {distance:.3g} points").format(otherShape=otherLabel,distance=overlapsOtherBottomBy))
if total:
if otherIsBehind:
# Translators: A message when a shape is infront of another shape on a Powerpoint slide
textList.append(_("covers {otherShape}").format(otherShape=otherLabel))
else:
# Translators: A message when a shape is behind another shape on a powerpoint slide
textList.append(_("behind {otherShape}").format(otherShape=otherLabel))
return ", ".join(textList)
def _get__edgeDistances(self):
slideWidth=self.appModule._ppApplication.activePresentation.pageSetup.slideWidth
slideHeight=self.appModule._ppApplication.activePresentation.pageSetup.slideHeight
leftDistance=self.ppObject.left
topDistance=self.ppObject.top
rightDistance=slideWidth-(leftDistance+self.ppObject.width)
bottomDistance=slideHeight-(topDistance+self.ppObject.height)
self._edgeDistances=(leftDistance,topDistance,rightDistance,bottomDistance)
return self._edgeDistances
def _getShapeLocationText(self,left=False,top=False,right=False,bottom=False):
leftDistance,topDistance,rightDistance,bottomDistance=self._edgeDistances
offSlideList=[]
onSlideList=[]
if left:
if leftDistance>=0:
# Translators: For a shape within a Powerpoint Slide, this is the distance in points from the shape's left edge to the slide's left edge
onSlideList.append(_("{distance:.3g} points from left slide edge").format(distance=leftDistance))
else:
# Translators: For a shape too far off the left edge of a Powerpoint Slide, this is the distance in points from the shape's left edge (off the slide) to the slide's left edge (where the slide starts)
offSlideList.append(_("Off left slide edge by {distance:.3g} points").format(distance=0-leftDistance))
if top:
if topDistance>=0:
# Translators: For a shape within a Powerpoint Slide, this is the distance in points from the shape's top edge to the slide's top edge
onSlideList.append(_("{distance:.3g} points from top slide edge").format(distance=topDistance))
else:
# Translators: For a shape too far off the top edge of a Powerpoint Slide, this is the distance in points from the shape's top edge (off the slide) to the slide's top edge (where the slide starts)
offSlideList.append(_("Off top slide edge by {distance:.3g} points").format(distance=0-topDistance))
if right:
if rightDistance>=0:
# Translators: For a shape within a Powerpoint Slide, this is the distance in points from the shape's right edge to the slide's right edge
onSlideList.append(_("{distance:.3g} points from right slide edge").format(distance=rightDistance))
else:
# Translators: For a shape too far off the right edge of a Powerpoint Slide, this is the distance in points from the shape's right edge (off the slide) to the slide's right edge (where the slide starts)
offSlideList.append(_("Off right slide edge by {distance:.3g} points").format(distance=0-rightDistance))
if bottom:
if bottomDistance>=0:
# Translators: For a shape within a Powerpoint Slide, this is the distance in points from the shape's bottom edge to the slide's bottom edge
onSlideList.append(_("{distance:.3g} points from bottom slide edge").format(distance=bottomDistance))
else:
# Translators: For a shape too far off the bottom edge of a Powerpoint Slide, this is the distance in points from the shape's bottom edge (off the slide) to the slide's bottom edge (where the slide starts)
offSlideList.append(_("Off bottom slide edge by {distance:.3g} points").format(distance=0-bottomDistance))
return ", ".join(offSlideList+onSlideList)
def _get_locationText(self):
textList=[]
text=self._getOverlapText()
if text:
textList.append(text)
text=self._getShapeLocationText(True,True,True,True)
if text:
textList.append(text)
return ", ".join(textList)
def _clearLocationCache(self):
try:
del self._overlapInfo
except AttributeError:
pass
try:
del self._edgeDistances
except AttributeError:
pass
def script_moveHorizontal(self,gesture):
gesture.send()
if scriptHandler.isScriptWaiting():
return
self._clearLocationCache()
textList=[]
text=self._getOverlapText()
if text:
textList.append(text)
text=self._getShapeLocationText(left=True,right=True)
if text:
textList.append(text)
ui.message(", ".join(textList))
def script_moveVertical(self,gesture):
gesture.send()
if scriptHandler.isScriptWaiting():
return
self._clearLocationCache()
textList=[]
text=self._getOverlapText()
if text:
textList.append(text)
text=self._getShapeLocationText(top=True,bottom=True)
if text:
textList.append(text)
ui.message(", ".join(textList))
def _get_ppPlaceholderType(self):
try:
return self.ppObject.placeholderFormat.type
except comtypes.COMError:
return None
def _get_location(self):
pointLeft=self.ppObject.left
pointTop=self.ppObject.top
pointWidth=self.ppObject.width
pointHeight=self.ppObject.height
left=self.documentWindow.ppObjectModel.pointsToScreenPixelsX(pointLeft)
top=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop)
right=self.documentWindow.ppObjectModel.pointsToScreenPixelsX(pointLeft+pointWidth)
bottom=self.documentWindow.ppObjectModel.pointsToScreenPixelsY(pointTop+pointHeight)
width=right-left
height=bottom-top
return (left,top,width,height)
def _get_ppShapeType(self):
"""Fetches and caches the type of this shape."""
self.ppShapeType=self.ppObject.type
return self.ppShapeType
def _get_name(self):
"""The name is calculated firstly from the object's title, otherwize if its a generic shape, then part of its programmatic name is used."""
#Powerpoint 2003 shape objects do not have a title property
try:
title=self.ppObject.title
except comtypes.COMError:
title=None
if title:
return title
if self.ppShapeType==msoPlaceholder:
label=ppPlaceholderLabels.get(self.ppPlaceholderType)
if label:
return label
if self.role==controlTypes.ROLE_SHAPE:
name=self.ppObject.name
return " ".join(name.split(' ')[:-1])
def _isEqual(self,other):
return super(Shape,self)._isEqual(other) and self.ppObject.ID==other.ppObject.ID
def _get_description(self):
return self.ppObject.alternativeText
def _get_role(self):
return msoShapeTypesToNVDARoles.get(self.ppShapeType,controlTypes.ROLE_SHAPE)
def _get_value(self):
if self.ppObject.hasTextFrame:
return self.ppObject.textFrame.textRange.text
def _get_states(self):
states=super(Shape,self).states
if self._overlapInfo[1] is not None:
states.add(controlTypes.STATE_OBSCURED)
if any(x for x in self._edgeDistances if x<0):
states.add(controlTypes.STATE_OFFSCREEN)
return states
def _get_mathMl(self):
try:
import mathType
except:
raise LookupError("MathType not installed")
obj = self.ppObject.OLEFormat
try:
# Don't call RunForConversion, as this seems to make focus bounce.
# We don't seem to need it for PowerPoint anyway.
return mathType.getMathMl(obj, runForConversion=False)
except:
raise LookupError("Couldn't get MathML from MathType")
__gestures={
"kb:leftArrow":"moveHorizontal",
"kb:rightArrow":"moveHorizontal",
"kb:upArrow":"moveVertical",
"kb:downArrow":"moveVertical",
"kb:shift+leftArrow":"moveHorizontal",
"kb:shift+rightArrow":"moveHorizontal",
"kb:shift+upArrow":"moveVertical",
"kb:shift+downArrow":"moveVertical",
"kb:enter":"selectionChange",
"kb:f2":"selectionChange",
}
class TextFrameTextInfo(textInfos.offsets.OffsetsTextInfo):
def _getCaretOffset(self):
return self.obj.documentWindow.ppSelection.textRange.start-1
def _getSelectionOffsets(self):
sel=self.obj.documentWindow.ppSelection.textRange
start=sel.start-1
end=start+sel.length
return start,end
def _getTextRange(self,start,end):
# #4619: First let's "normalise" the text, i.e. get rid of the CR/LF mess
text=self.obj.ppObject.textRange.text
text=text.replace('\r\n','\n')
#Now string slicing will be okay
text=text[start:end].replace('\x0b','\n')
text=text.replace('\r','\n')
return text
def _getStoryLength(self):
return self.obj.ppObject.textRange.length
def _getLineOffsets(self,offset):
#Seems to be no direct way to find the line offsets for a given offset.
#Therefore walk through all the lines until one surrounds the offset.
lines=self.obj.ppObject.textRange.lines()
length=lines.length
# #3403: handle case where offset is at end of the text in in a control with only one line
# The offset should be limited to the last offset in the text, but only if the text does not end in a line feed.
if length and offset>=length and self._getTextRange(length-1,length)!='\n':
offset=min(offset,length-1)
for line in lines:
start=line.start-1
end=start+line.length
if start<=offset<end:
return start,end
return offset,offset+1
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True):
formatField=textInfos.FormatField()
curRun=None
if calculateOffsets:
runs=self.obj.ppObject.textRange.runs()
for run in runs:
start=run.start-1
end=start+run.length
if start<=offset<end:
startOffset=start
endOffset=end
curRun=run
break
if not curRun:
curRun=self.obj.ppObject.textRange.characters(offset+1)
startOffset,endOffset=offset,self._endOffset
if self._startOffset==0 or offset==self._startOffset and self._getTextRange(offset-1,offset).startswith('\n'):
b=curRun.paragraphFormat.bullet
bulletText=getBulletText(b)
if bulletText:
formatField['line-prefix']=bulletText
font=curRun.font
if formatConfig['reportFontName']:
formatField['font-name']=font.name
if formatConfig['reportFontSize']:
formatField['font-size']=str(font.size)
if formatConfig['reportFontAttributes']:
formatField['bold']=bool(font.bold)
formatField['italic']=bool(font.italic)
formatField['underline']=bool(font.underline)
if font.subscript:
formatField['text-position']='sub'
elif font.superscript:
formatField['text-position']='super'
if formatConfig['reportColor']:
formatField['color']=colors.RGB.fromCOLORREF(font.color.rgb)
if formatConfig["reportLinks"] and curRun.actionSettings(ppMouseClick).action==ppActionHyperlink:
formatField["link"]=True
return formatField,(startOffset,endOffset)
class Table(Shape):
"""Represents the table shape in Powerpoint. Provides row and column counts."""
def _get_ppTable(self):
self.ppTable=self.ppObject.table
return self.ppTable
def _get_columnCount(self):
return self.ppTable.columns.count
def _get_rowCount(self):
return self.ppTable.rows.count
class TableCell(PpObject):
"""Represents a table cell in Powerpoint. Accepts a table and a row and column number as this cannot be calculated directly."""
name=None
role=controlTypes.ROLE_TABLECELL
def _isEqual(self,other):
return self.table==other.table and (self.columnNumber,self.rowNumber)==(other.columnNumber,other.rowNumber)
def __init__(self,windowHandle=None,documentWindow=None,ppObject=None,table=None,rowNumber=None,columnNumber=None):
self.parent=self.table=table
self.columnNumber=columnNumber
self.rowNumber=rowNumber
super(TableCell,self).__init__(windowHandle=windowHandle,documentWindow=documentWindow,ppObject=ppObject)
class TextFrame(EditableTextWithoutAutoSelectDetection,PpObject):
"""Represents a Text frame in Powerpoint. Provides a suitable TextInfo."""
TextInfo=TextFrameTextInfo
def __init__(self,windowHandle=None,documentWindow=None,ppObject=None):
super(TextFrame,self).__init__(windowHandle=windowHandle,documentWindow=documentWindow,ppObject=ppObject)
# EditableText* wasn't added as an overlay,
# so initOverlayClass doesn't get called automatically.
# #5360: EditableText.initOverlayClass gives us announcement of new line text.
EditableText.initOverlayClass(self)
EditableTextWithoutAutoSelectDetection.initClass(self)
def _isEqual(self,other):
return super(TextFrame,self)._isEqual(other) and self.ppObject.parent.ID==other.ppObject.parent.ID
name=None
role=controlTypes.ROLE_EDITABLETEXT
states = {controlTypes.STATE_MULTILINE}
def _get_parent(self):
parent=self.ppObject.parent
if parent:
return Shape(windowHandle=self.windowHandle,documentWindow=self.documentWindow,ppObject=parent)
def script_caret_backspaceCharacter(self, gesture):
super(TextFrame, self).script_caret_backspaceCharacter(gesture)
# #3231: The typedCharacter event is never fired for the backspace key.
# Call it here so that speak typed words works as expected.
self.event_typedCharacter(u"\b")
class TableCellTextFrame(TextFrame):
"""Represents a text frame inside a table cell in Powerpoint. Specifially supports the caret jumping into another cell with tab or arrows."""
def _isEqual(self,other):
return self.parent==other.parent
__gestures={
"kb:tab":"selectionChange",
"kb:shift+tab":"selectionChange",
}
class NotesTextFrame(TextFrame):
def _get_parent(self):
return self.documentWindow
class SlideShowTreeInterceptorTextInfo(NVDAObjectTextInfo):
"""The TextInfo for Slide Show treeInterceptors. Based on NVDAObjectTextInfo but tweeked to work with TreeInterceptors by using basicText on the treeInterceptor's rootNVDAObject."""
def _getStoryText(self):
return self.obj.rootNVDAObject.basicText
def _getOffsetsFromNVDAObject(self,obj):
if obj==self.obj.rootNVDAObject:
return (0,self._getStoryLength())
raise LookupError
def getTextWithFields(self,formatConfig=None):
fields = self.obj.rootNVDAObject.basicTextFields
text = self.obj.rootNVDAObject.basicText
out = []
textOffset = self._startOffset
for fieldOffset, field in fields:
if fieldOffset < self._startOffset:
continue
elif fieldOffset >= self._endOffset:
break
# Output any text before the field.
chunk = text[textOffset:fieldOffset]
if chunk:
out.append(chunk)
# Output the field.
out.extend((
# Copy the field so the original isn't modified.
textInfos.FieldCommand("controlStart", textInfos.ControlField(field)),
u" ", textInfos.FieldCommand("controlEnd", None)))
textOffset = fieldOffset + 1
# Output any text after all fields in this range.
chunk = text[textOffset:self._endOffset]
if chunk:
out.append(chunk)
return out
def getMathMl(self, field):
try:
import mathType
except:
raise LookupError("MathType not installed")
try:
# Don't call RunForConversion, as this seems to make focus bounce.
# We don't seem to need it for PowerPoint anyway.
return mathType.getMathMl(field["oleFormat"], runForConversion=False)
except:
raise LookupError("Couldn't get MathML from MathType")
class SlideShowTreeInterceptor(DocumentTreeInterceptor):
"""A TreeInterceptor for showing Slide show content. Has no caret navigation, a CursorManager must be used on top. """
def _get_isAlive(self):
return winUser.isWindow(self.rootNVDAObject.windowHandle)
def __contains__(self,obj):
return isinstance(obj,Window) and obj.windowHandle==self.rootNVDAObject.windowHandle
hadFocusOnce=False
def event_treeInterceptor_gainFocus(self):
braille.handler.handleGainFocus(self)
self.rootNVDAObject.reportFocus()
if not self.hadFocusOnce:
self.hadFocusOnce=True
self.reportNewSlide()
else:
info = self.selection
if not info.isCollapsed:
speech.speakSelectionMessage(_("selected %s"), info.text)
else:
info.expand(textInfos.UNIT_LINE)
speech.speakTextInfo(info, reason=controlTypes.REASON_CARET, unit=textInfos.UNIT_LINE)
def event_gainFocus(self,obj,nextHandler):
pass
TextInfo=SlideShowTreeInterceptorTextInfo
def makeTextInfo(self,position):
return self.TextInfo(self,position)
def reportNewSlide(self):
self.makeTextInfo(textInfos.POSITION_FIRST).updateCaret()
sayAllHandler.readText(sayAllHandler.CURSOR_CARET)
def script_toggleNotesMode(self,gesture):
self.rootNVDAObject.notesMode=not self.rootNVDAObject.notesMode
self.rootNVDAObject.handleSlideChange()
# Translators: The description for a script
script_toggleNotesMode.__doc__=_("Toggles between reporting the speaker notes or the actual slide content. This does not change what is visible on-screen, but only what the user can read with NVDA")
def script_slideChange(self,gesture):
gesture.send()
self.rootNVDAObject.handleSlideChange()
class ReviewableSlideshowTreeInterceptor(ReviewCursorManager,SlideShowTreeInterceptor):
"""A TreeInterceptor for Slide show content but with caret navigation via ReviewCursorManager."""
__gestures={
"kb:space":"slideChange",
"kb:enter":"slideChange",
"kb:backspace":"slideChange",
"kb:pageUp":"slideChange",
"kb:pageDown":"slideChange",
"kb:control+shift+s":"toggleNotesMode",
}
class SlideShowWindow(PaneClassDC):
_lastSlideChangeID=None
treeInterceptorClass=ReviewableSlideshowTreeInterceptor
notesMode=False #: If true then speaker notes will be exposed as this object's basicText, rather than the actual slide content.
def _get_name(self):
if self.currentSlide:
if self.notesMode:
# Translators: The title of the current slide (with notes) in a running Slide Show in Microsoft PowerPoint.
return _("Slide show notes - {slideName}").format(slideName=self.currentSlide.name)
else:
# Translators: The title of the current slide in a running Slide Show in Microsoft PowerPoint.
return _("Slide show - {slideName}").format(slideName=self.currentSlide.name)
else:
# Translators: The title for a Slide show in Microsoft PowerPoint that has completed.
return _("Slide Show - complete")
value=None
def _getShapeText(self,shape,cellShape=False):
if shape.hasTextFrame:
for p in shape.textFrame.textRange.paragraphs():
bulletText=getBulletText(p.paragraphFormat.bullet)
text=p.text.replace('\x0b','\n')
text=text.replace('\r','\n')
text=text.rstrip()
text=" ".join([t for t in (bulletText,text) if t])
if text and not text.isspace():
yield text
return
if cellShape: return
shapeType=shape.type
if shapeType==msoGroup:
for childShape in shape.groupItems:
for chunk in self._getShapeText(childShape):
yield chunk
return
if shape.hasTable:
table=shape.table
for row in table.rows:
for cell in row.cells:
for chunk in self._getShapeText(cell.shape,cellShape=True):
yield chunk
return
if shapeType==msoEmbeddedOLEObject:
oleFormat=shape.OLEFormat
if oleFormat.ProgID.startswith(MATHTYPE_PROGID):
yield textInfos.ControlField(role=controlTypes.ROLE_MATH,
oleFormat=oleFormat, _startOfNode=True)
return
label=shape.alternativeText
if not label:
try:
label=shape.title
except comtypes.COMError:
pass
if label:
typeName=" ".join(shape.name.split(' ')[:-1])
if typeName and not typeName.isspace():
yield "%s %s"%(typeName,label)
else:
yield label
def _get_basicText(self):
if not self.currentSlide:
return self.name
chunks=[]
ppObject=self.currentSlide.ppObject
if self.notesMode:
ppObject=ppObject.notesPage
# Maintain a list of fields and the offsets at which they occur.
# For now, these are only control fields that consume a space.
fields=self.basicTextFields=[]
# Incrementation of textLen must include line feed added by join.
textLen=0
for shape in ppObject.shapes:
for chunk in self._getShapeText(shape):
if isinstance(chunk,textInfos.ControlField):
fields.append((textLen,chunk))
chunks.append(" ")
textLen+=2
else:
chunks.append(chunk)
textLen+=len(chunk)+1
self.basicText="\n".join(chunks)
if not self.basicText:
if self.notesMode:
# Translators: The message for no notes for a slide in a slide show
self.basicText=_("No notes")
else:
# Translators: The message for an empty slide in a slide show
self.basicText=_("Empty slide")
return self.basicText or _("Empty slide")
def handleSlideChange(self):
try:
del self.__dict__['currentSlide']
except KeyError:
pass
curSlideChangeID=self.name
if curSlideChangeID==self._lastSlideChangeID:
return
self._lastSlideChangeID=curSlideChangeID
try:
del self.__dict__['basicText']
except KeyError:
pass
self.reportFocus()
self.treeInterceptor.reportNewSlide()
class AppModule(appModuleHandler.AppModule):
hasTriedPpAppSwitch=False
_ppApplicationWindow=None
_ppApplication=None
_ppEApplicationConnectionPoint=None
def isBadUIAWindow(self,hwnd):
# PowerPoint 2013 implements UIA support for its slides etc on an mdiClass window. However its far from complete.
# We must disable it in order to fall back to our own code.
if winUser.getClassName(hwnd) in objectModelWindowClasses:
return True
return super(AppModule,self).isBadUIAWindow(hwnd)
def _registerCOMWithFocusJuggle(self):
import wx
import gui
# Translators: A title for a dialog shown while Microsoft PowerPoint initializes
d=wx.Dialog(None,title=_("Waiting for Powerpoint..."))
d.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
gui.mainFrame.prePopup()
d.Show()
self.hasTriedPpAppSwitch=True
#Make sure NVDA detects and reports focus on the waiting dialog
api.processPendingEvents()
comtypes.client.PumpEvents(1)
d.Destroy()
gui.mainFrame.postPopup()
def _getPpObjectModelFromWindow(self,windowHandle):
"""
Fetches the Powerpoint object model from a given window.
"""
try:
pDispatch=oleacc.AccessibleObjectFromWindow(windowHandle,winUser.OBJID_NATIVEOM,interface=comtypes.automation.IDispatch)
return comtypes.client.dynamic.Dispatch(pDispatch)
except:
log.debugWarning("Could not get MS Powerpoint object model",exc_info=True)
return None
_ppApplicationFromROT=None
def _getPpObjectModelFromROT(self,useRPC=False):
if not self._ppApplicationFromROT:
try:
self._ppApplicationFromROT=comHelper.getActiveObject(u'powerPoint.application',dynamic=True,appModule=self if useRPC else None)
except:
log.debugWarning("Could not get active object via RPC")
return None
try:
pres=self._ppApplicationFromROT.activePresentation
except (comtypes.COMError,NameError,AttributeError):
log.debugWarning("No active presentation")
return None
try:
ppSlideShowWindow=pres.slideShowWindow
except (comtypes.COMError,NameError,AttributeError):
log.debugWarning("Could not get slideShowWindow")
ppSlideShowWindow=None
isActiveSlideShow=False
if ppSlideShowWindow:
try:
isActiveSlideShow=ppSlideShowWindow.active
except comtypes.COMError:
log.debugWarning("slideShowWindow.active",exc_info=True)
if isActiveSlideShow:
return ppSlideShowWindow
try:
window=pres.windows.item(1)
except (comtypes.COMError,NameError,AttributeError):
window=None
return window
def _fetchPpObjectModelHelper(self,windowHandle=None):
m=None
# Its only safe to get the object model from PowerPoint 2003 to 2010 windows.
# In PowerPoint 2013 protected mode it causes security/stability issues
if windowHandle and winUser.getClassName(windowHandle)=="paneClassDC":
m=self._getPpObjectModelFromWindow(windowHandle)
if not m:
m=self._getPpObjectModelFromROT(useRPC=True)
if not m:
m=self._getPpObjectModelFromROT()
return m
def fetchPpObjectModel(self,windowHandle=None):
m=self._fetchPpObjectModelHelper(windowHandle=windowHandle)
if not m and not self.hasTriedPpAppSwitch:
self._registerCOMWithFocusJuggle()
m=self._fetchPpObjectModelHelper(windowHandle=windowHandle)
if m:
if windowHandle!=self._ppApplicationWindow or not self._ppApplication:
self._ppApplicationWindow=windowHandle
self._ppApplication=m.application
sink=ppEApplicationSink().QueryInterface(comtypes.IUnknown)
self._ppEApplicationConnectionPoint=comtypes.client._events._AdviseConnection(self._ppApplication,EApplication,sink)
return m
def chooseNVDAObjectOverlayClasses(self,obj,clsList):
if obj.windowClassName in objectModelWindowClasses and isinstance(obj,IAccessible) and not isinstance(obj,PpObject) and obj.event_objectID==winUser.OBJID_CLIENT and controlTypes.STATE_FOCUSED in obj.states:
m=self.fetchPpObjectModel(windowHandle=obj.windowHandle)
if not m:
log.debugWarning("no object model")
return
try:
ppActivePaneViewType=m.activePane.viewType
except comtypes.COMError:
ppActivePaneViewType=None
if ppActivePaneViewType is None:
clsList.insert(0,SlideShowWindow)
elif ppActivePaneViewType==ppViewOutline:
clsList.insert(0,OutlinePane)
else:
clsList.insert(0,DocumentWindow)
obj.ppActivePaneViewType=ppActivePaneViewType
obj.ppObjectModel=m