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