Skip to content

Commit 410b90b

Browse files
Merge branch 't1987'. Fixes #1987
2 parents 56cd8df + 57a2731 commit 410b90b

File tree

4 files changed

+1192
-4
lines changed

4 files changed

+1192
-4
lines changed

source/NVDAObjects/window/excel.py

+269-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#NVDAObjects/excel.py
22
#A part of NonVisual Desktop Access (NVDA)
3-
#Copyright (C) 2006-2015 NV Access Limited, Siddhartha Gupta
3+
#Copyright (C) 2006-2015 NV Access Limited, Dinesh Kaushal, Siddhartha Gupta
44
#This file is covered by the GNU General Public License.
55
#See the file COPYING for more details.
66

@@ -27,6 +27,7 @@
2727
from . import Window
2828
from .. import NVDAObjectTextInfo
2929
import scriptHandler
30+
import browseMode
3031
import ctypes
3132

3233
xlCenter=-4108
@@ -52,8 +53,255 @@
5253
xlRC = 2
5354
xlUnderlineStyleNone=-4142
5455

56+
#Excel cell types
57+
xlCellTypeAllFormatConditions =-4172 # from enum XlCellType
58+
xlCellTypeAllValidation =-4174 # from enum XlCellType
59+
xlCellTypeBlanks =4 # from enum XlCellType
60+
xlCellTypeComments =-4144 # from enum XlCellType
61+
xlCellTypeConstants =2 # from enum XlCellType
62+
xlCellTypeFormulas =-4123 # from enum XlCellType
63+
xlCellTypeLastCell =11 # from enum XlCellType
64+
xlCellTypeSameFormatConditions=-4173 # from enum XlCellType
65+
xlCellTypeSameValidation =-4175 # from enum XlCellType
66+
xlCellTypeVisible =12 # from enum XlCellType
67+
68+
re_RC=re.compile(r'R(?:\[(\d+)\])?C(?:\[(\d+)\])?')
5569
re_absRC=re.compile(r'^R(\d+)C(\d+)(?::R(\d+)C(\d+))?$')
5670

71+
class ExcelQuickNavItem(browseMode.QuickNavItem):
72+
73+
def __init__( self , nodeType , document , itemObject , itemCollection ):
74+
self.excelItemObject = itemObject
75+
self.excelItemCollection = itemCollection
76+
super( ExcelQuickNavItem ,self).__init__( nodeType , document )
77+
78+
def activate(self):
79+
pass
80+
81+
def isChild(self,parent):
82+
return False
83+
84+
def report(self,readUnit=None):
85+
pass
86+
87+
class ExcelChartQuickNavItem(ExcelQuickNavItem):
88+
89+
def __init__( self , nodeType , document , chartObject , chartCollection ):
90+
self.chartIndex = chartObject.Index
91+
if chartObject.Chart.HasTitle:
92+
93+
self.label = chartObject.Chart.ChartTitle.Text + " " + chartObject.TopLeftCell.address(False,False,1,False) + "-" + chartObject.BottomRightCell.address(False,False,1,False)
94+
95+
else:
96+
97+
self.label = chartObject.Name + " " + chartObject.TopLeftCell.address(False,False,1,False) + "-" + chartObject.BottomRightCell.address(False,False,1,False)
98+
99+
super( ExcelChartQuickNavItem ,self).__init__( nodeType , document , chartObject , chartCollection )
100+
101+
def __lt__(self,other):
102+
return self.chartIndex < other.chartIndex
103+
104+
def moveTo(self):
105+
try:
106+
self.excelItemObject.Activate()
107+
108+
# After activate(), though the chart object is selected,
109+
110+
# pressing arrow keys moves the object, rather than
111+
112+
# let use go inside for sub-objects. Somehow
113+
# calling an COM function on a different object fixes that !
114+
115+
log.debugWarning( self.excelItemCollection.Count )
116+
117+
except(COMError):
118+
119+
pass
120+
focus=api.getDesktopObject().objectWithFocus()
121+
if not focus or not isinstance(focus,ExcelBase):
122+
return
123+
# Charts are not yet automatically detected with objectFromFocus, so therefore use selection
124+
sel=focus._getSelection()
125+
if not sel:
126+
return
127+
eventHandler.queueEvent("gainFocus",sel)
128+
129+
130+
@property
131+
def isAfterSelection(self):
132+
activeCell = self.document.Application.ActiveCell
133+
#log.debugWarning("active row: {} active column: {} current row: {} current column: {}".format ( activeCell.row , activeCell.column , self.excelCommentObject.row , self.excelCommentObject.column ) )
134+
135+
if self.excelItemObject.TopLeftCell.row == activeCell.row:
136+
if self.excelItemObject.TopLeftCell.column > activeCell.column:
137+
return False
138+
elif self.excelItemObject.TopLeftCell.row > activeCell.row:
139+
return False
140+
return True
141+
142+
class ExcelRangeBasedQuickNavItem(ExcelQuickNavItem):
143+
144+
def __lt__(self,other):
145+
if self.excelItemObject.row == other.excelItemObject.row:
146+
return self.excelItemObject.column < other.excelItemObject.column
147+
else:
148+
return self.excelItemObject.row < other.excelItemObject.row
149+
150+
def moveTo(self):
151+
self.excelItemObject.Activate()
152+
eventHandler.queueEvent("gainFocus",api.getDesktopObject().objectWithFocus())
153+
154+
@property
155+
def isAfterSelection(self):
156+
activeCell = self.document.Application.ActiveCell
157+
log.debugWarning("active row: {} active column: {} current row: {} current column: {}".format ( activeCell.row , activeCell.column , self.excelItemObject.row , self.excelItemObject.column ) )
158+
159+
if self.excelItemObject.row == activeCell.row:
160+
if self.excelItemObject.column > activeCell.column:
161+
return False
162+
elif self.excelItemObject.row > activeCell.row:
163+
return False
164+
return True
165+
166+
class ExcelCommentQuickNavItem(ExcelRangeBasedQuickNavItem):
167+
168+
def __init__( self , nodeType , document , commentObject , commentCollection ):
169+
self.label = commentObject.address(False,False,1,False) + " " + commentObject.Comment.Text()
170+
super( ExcelCommentQuickNavItem , self).__init__( nodeType , document , commentObject , commentCollection )
171+
172+
class ExcelFormulaQuickNavItem(ExcelRangeBasedQuickNavItem):
173+
174+
def __init__( self , nodeType , document , formulaObject , formulaCollection ):
175+
self.label = formulaObject.address(False,False,1,False) + " " + formulaObject.Formula
176+
super( ExcelFormulaQuickNavItem , self).__init__( nodeType , document , formulaObject , formulaCollection )
177+
178+
class ExcelQuicknavIterator(object):
179+
"""
180+
Allows iterating over an MS excel collection (e.g. Comments, Formulas or charts) emitting L{QuickNavItem} objects.
181+
"""
182+
183+
def __init__(self, itemType , document , direction , includeCurrent):
184+
"""
185+
See L{QuickNavItemIterator} for itemType, document and direction definitions.
186+
@ param includeCurrent: if true then any item at the initial position will be also emitted rather than just further ones.
187+
"""
188+
self.document=document
189+
self.itemType=itemType
190+
self.direction=direction if direction else "next"
191+
self.includeCurrent=includeCurrent
192+
193+
def collectionFromWorksheet(self,worksheetObject):
194+
"""
195+
Fetches a Microsoft Excel collection object from a Microsoft excel worksheet object. E.g. charts, comments, or formula.
196+
@param worksheetObject: a Microsoft excel worksheet object.
197+
@return: a Microsoft excel collection object.
198+
"""
199+
raise NotImplementedError
200+
201+
def filter(self,item):
202+
"""
203+
Only allows certain items fom a collection to be emitted. E.g. a chart .
204+
@param item: an item from a Microsoft excel collection (e.g. chart object).
205+
@return True if this item should be allowd, false otherwise.
206+
@rtype: bool
207+
"""
208+
return True
209+
210+
def iterate(self):
211+
"""
212+
returns a generator that emits L{QuickNavItem} objects for this collection.
213+
"""
214+
items=self.collectionFromWorksheet(self.document)
215+
if not items:
216+
return
217+
if self.direction=="previous":
218+
items=reversed(items)
219+
for collectionItem in items:
220+
item=self.quickNavItemClass(self.itemType,self.document,collectionItem , items )
221+
if not self.filter(collectionItem):
222+
continue
223+
yield item
224+
225+
class ChartExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
226+
quickNavItemClass=ExcelChartQuickNavItem#: the QuickNavItem class that should be instanciated and emitted.
227+
def collectionFromWorksheet( self , worksheetObject ):
228+
return worksheetObject.ChartObjects()
229+
230+
class CommentExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
231+
quickNavItemClass=ExcelCommentQuickNavItem#: the QuickNavItem class that should be instanciated and emitted.
232+
def collectionFromWorksheet( self , worksheetObject ):
233+
try:
234+
return worksheetObject.cells.SpecialCells( xlCellTypeComments )
235+
except(COMError):
236+
237+
return None
238+
239+
class FormulaExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
240+
quickNavItemClass=ExcelFormulaQuickNavItem#: the QuickNavItem class that should be instanciated and emitted.
241+
def collectionFromWorksheet( self , worksheetObject ):
242+
try:
243+
return worksheetObject.cells.SpecialCells( xlCellTypeFormulas )
244+
except(COMError):
245+
246+
return None
247+
248+
class ExcelBrowseModeTreeInterceptor(browseMode.BrowseModeTreeInterceptor):
249+
250+
needsReviewCursorTextInfoWrapper=False
251+
passThrough=True
252+
253+
def _get_isAlive(self):
254+
if not winUser.isWindow(self.rootNVDAObject.windowHandle):
255+
return False
256+
try:
257+
return self.rootNVDAObject.excelWorksheetObject.name==self.rootNVDAObject.excelApplicationObject.activeSheet.name
258+
except (COMError,AttributeError,NameError):
259+
log.debugWarning("could not compair sheet names",exc_info=True)
260+
return False
261+
262+
263+
def __contains__(self,obj):
264+
return winUser.isDescendantWindow(self.rootNVDAObject.windowHandle,obj.windowHandle)
265+
266+
267+
268+
def _set_selection(self,info):
269+
super(ExcelBrowseModeTreeInterceptor,self)._set_selection(info)
270+
#review.handleCaretMove(info)
271+
272+
def _get_ElementsListDialog(self):
273+
return ElementsListDialog
274+
275+
def _iterNodesByType(self,nodeType,direction="next",pos=None):
276+
if nodeType=="chart":
277+
return ChartExcelCollectionQuicknavIterator( nodeType , self.rootNVDAObject.excelWorksheetObject , direction , None ).iterate()
278+
elif nodeType=="comment":
279+
return CommentExcelCollectionQuicknavIterator( nodeType , self.rootNVDAObject.excelWorksheetObject , direction , None ).iterate()
280+
elif nodeType=="formula":
281+
return FormulaExcelCollectionQuicknavIterator( nodeType , self.rootNVDAObject.excelWorksheetObject , direction , None ).iterate()
282+
else:
283+
raise NotImplementedError
284+
285+
def script_elementsList(self,gesture):
286+
super(ExcelBrowseModeTreeInterceptor,self).script_elementsList(gesture)
287+
# Translators: the description for the elements list dialog script on virtualBuffers.
288+
script_elementsList.__doc__ = _("Presents a list of links, headings or landmarks")
289+
script_elementsList.ignoreTreeInterceptorPassThrough=True
290+
291+
class ElementsListDialog(browseMode.ElementsListDialog):
292+
293+
ELEMENT_TYPES=(
294+
# Translators: The label of a radio button to select the type of element
295+
# in the browse mode Elements List dialog.
296+
("chart", _("&Chart")),
297+
# Translators: The label of a radio button to select the type of element
298+
# in the browse mode Elements List dialog.
299+
("comment", _("C&omment")),
300+
# Translators: The label of a radio button to select the type of element
301+
# in the browse mode Elements List dialog.
302+
("formula", _("&Formula")),
303+
)
304+
57305
class ExcelBase(Window):
58306
"""A base that all Excel NVDAObjects inherit from, which contains some useful methods."""
59307

@@ -99,14 +347,27 @@ def _getSelection(self):
99347
isMerged=selection.mergeCells
100348
except (COMError,NameError):
101349
isMerged=False
350+
351+
try:
352+
numCells=selection.count
353+
except (COMError,NameError):
354+
numCells=0
355+
356+
isChartActive = True if self.excelWindowObject.ActiveChart else False
357+
obj=None
102358
if isMerged:
103359
obj=ExcelMergedCell(windowHandle=self.windowHandle,excelWindowObject=self.excelWindowObject,excelCellObject=selection.item(1))
104-
elif selection.Count>1:
360+
elif numCells>1:
105361
obj=ExcelSelection(windowHandle=self.windowHandle,excelWindowObject=self.excelWindowObject,excelRangeObject=selection)
106-
else:
362+
elif numCells==1:
107363
obj=ExcelCell(windowHandle=self.windowHandle,excelWindowObject=self.excelWindowObject,excelCellObject=selection)
364+
elif isChartActive:
365+
selection = self.excelWindowObject.ActiveChart
366+
import excelChart
367+
obj=excelChart.ExcelChart(windowHandle=self.windowHandle,excelWindowObject=self.excelWindowObject,excelChartObject=selection)
108368
return obj
109369

370+
110371
class Excel7Window(ExcelBase):
111372
"""An overlay class for Window for the EXCEL7 window class, which simply bounces focus to the active excel cell."""
112373

@@ -126,6 +387,9 @@ def event_gainFocus(self):
126387

127388
class ExcelWorksheet(ExcelBase):
128389

390+
391+
treeInterceptorClass=ExcelBrowseModeTreeInterceptor
392+
129393
role=controlTypes.ROLE_TABLE
130394

131395
def _get_excelApplicationObject(self):
@@ -880,9 +1144,10 @@ class ExcelMergedCell(ExcelCell):
8801144

8811145
def _get_cellCoordsText(self):
8821146
return self.getCellAddress(self.excelCellObject.mergeArea)
883-
1147+
8841148
def _get_rowSpan(self):
8851149
return self.excelCellObject.mergeArea.rows.count
8861150

8871151
def _get_colSpan(self):
8881152
return self.excelCellObject.mergeArea.columns.count
1153+

0 commit comments

Comments
 (0)