1
1
#NVDAObjects/excel.py
2
2
#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
4
4
#This file is covered by the GNU General Public License.
5
5
#See the file COPYING for more details.
6
6
27
27
from . import Window
28
28
from .. import NVDAObjectTextInfo
29
29
import scriptHandler
30
+ import browseMode
30
31
import ctypes
31
32
32
33
xlCenter = - 4108
52
53
xlRC = 2
53
54
xlUnderlineStyleNone = - 4142
54
55
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+)\])?' )
55
69
re_absRC = re .compile (r'^R(\d+)C(\d+)(?::R(\d+)C(\d+))?$' )
56
70
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
+
57
305
class ExcelBase (Window ):
58
306
"""A base that all Excel NVDAObjects inherit from, which contains some useful methods."""
59
307
@@ -99,14 +347,27 @@ def _getSelection(self):
99
347
isMerged = selection .mergeCells
100
348
except (COMError ,NameError ):
101
349
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
102
358
if isMerged :
103
359
obj = ExcelMergedCell (windowHandle = self .windowHandle ,excelWindowObject = self .excelWindowObject ,excelCellObject = selection .item (1 ))
104
- elif selection . Count > 1 :
360
+ elif numCells > 1 :
105
361
obj = ExcelSelection (windowHandle = self .windowHandle ,excelWindowObject = self .excelWindowObject ,excelRangeObject = selection )
106
- else :
362
+ elif numCells == 1 :
107
363
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 )
108
368
return obj
109
369
370
+
110
371
class Excel7Window (ExcelBase ):
111
372
"""An overlay class for Window for the EXCEL7 window class, which simply bounces focus to the active excel cell."""
112
373
@@ -126,6 +387,9 @@ def event_gainFocus(self):
126
387
127
388
class ExcelWorksheet (ExcelBase ):
128
389
390
+
391
+ treeInterceptorClass = ExcelBrowseModeTreeInterceptor
392
+
129
393
role = controlTypes .ROLE_TABLE
130
394
131
395
def _get_excelApplicationObject (self ):
@@ -880,9 +1144,10 @@ class ExcelMergedCell(ExcelCell):
880
1144
881
1145
def _get_cellCoordsText (self ):
882
1146
return self .getCellAddress (self .excelCellObject .mergeArea )
883
-
1147
+
884
1148
def _get_rowSpan (self ):
885
1149
return self .excelCellObject .mergeArea .rows .count
886
1150
887
1151
def _get_colSpan (self ):
888
1152
return self .excelCellObject .mergeArea .columns .count
1153
+
0 commit comments