Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ typedef struct {
BSTR address;
BSTR inputTitle;
BSTR inputMessage;
hyper states;
hyper nvCellStates; // bitwise OR of the NvCellState enum values that apply to this cell.
long rowNumber;
long rowSpan;
long columnNumber;
Expand Down
32 changes: 17 additions & 15 deletions nvdaHelper/remote/excel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ long getCellTextWidth(HWND hwnd, IDispatch* pDispatchRange) {
}

__int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
__int64 states=0;
std::int64_t nvCellStates = 0;
// If the current row is a summary row, expose the collapsed or expanded states depending on wither the inner rows are showing or not.
CComPtr<IDispatch> pDispatchRow=nullptr;
HRESULT res=_com_dispatch_raw_propget(pDispatchRange,XLDISPID_RANGE_ENTIREROW,VT_DISPATCH,&pDispatchRow);
Expand All @@ -129,11 +129,13 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
if(FAILED(res)) {
LOG_DEBUGWARNING(L"row.showDetail failed with code "<<res);
}
states|=(showDetail?NVSTATE_EXPANDED:NVSTATE_COLLAPSED);
nvCellStates |= (showDetail ? NvCellState::EXPANDED : NvCellState::COLLAPSED);
}
}
// If this row was neither collapsed or expanded, then try the same for columns instead.
if(!(states&NVSTATE_EXPANDED)&&!(states&NVSTATE_COLLAPSED)) {
if( // Row not collapsed or expanded, try the same for columns instead.
!(nvCellStates& NvCellState::EXPANDED)
&&!(nvCellStates & NvCellState::COLLAPSED)
) {
CComPtr<IDispatch> pDispatchColumn=nullptr;
res=_com_dispatch_raw_propget(pDispatchRange,XLDISPID_RANGE_ENTIRECOLUMN,VT_DISPATCH,&pDispatchColumn);
if(FAILED(res)) {
Expand All @@ -151,7 +153,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
if(FAILED(res)) {
LOG_DEBUGWARNING(L"column.showDetail failed with code "<<res);
}
states|=(showDetail?NVSTATE_EXPANDED:NVSTATE_COLLAPSED);
nvCellStates |= (showDetail ? NvCellState::EXPANDED : NvCellState::COLLAPSED);
}
}
}
Expand All @@ -162,7 +164,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"range.hasFormula failed with code "<<res);
}
if(hasFormula) {
states|=NVSTATE_HASFORMULA;
nvCellStates |= NvCellState::HASFORMULA;
}
// Expose whether this cell has a dropdown menu for choosing valid values
CComPtr<IDispatch> pDispatchValidation=nullptr;
Expand All @@ -179,7 +181,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"validation.type failed with code "<<res);
}
if(validationType==xlValidateList) {
states|=NVSTATE_HASPOPUP;
nvCellStates |= NvCellState::HASPOPUP;
}
}
// Expose whether this cell has comments
Expand All @@ -189,7 +191,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"range.comment failed with code "<<res);
}
if(pDispatchComment) {
states|=NVSTATE_HASCOMMENT;
nvCellStates |= NvCellState::HASCOMMENT;
}
// Expose whether this cell is unlocked for editing
BOOL locked=false;
Expand All @@ -210,7 +212,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"worksheet.protectcontents failed with code "<<res);
}
if(protectContents) {
states|=NVSTATE_UNLOCKED;
nvCellStates |= NvCellState::UNLOCKED;
}
}
}
Expand All @@ -227,7 +229,7 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"hyperlinks.count failed with code "<<res);
}
if(count>0) {
states|=NVSTATE_LINKED;
nvCellStates |= NvCellState::LINKED;
}
}
// Expose whether this cell's content flows outside the cell,
Expand Down Expand Up @@ -304,17 +306,17 @@ __int64 getCellStates(HWND hwnd, IDispatch* pDispatchRange) {
LOG_DEBUGWARNING(L"range.text failed with code "<<res);
}
if(text&&text.Length()>0) {
states|=NVSTATE_CROPPED;
nvCellStates |= NvCellState::CROPPED;
}
}
if(!(states&NVSTATE_CROPPED)) {
states|=NVSTATE_OVERFLOWING;
if(!(nvCellStates & NvCellState::CROPPED)) {
nvCellStates |= NvCellState::OVERFLOWING;
}
}
}
}
}
return states;
return nvCellStates;
}

HRESULT getCellInfo(HWND hwnd, IDispatch* pDispatchRange, long cellInfoFlags, EXCEL_CELLINFO* cellInfo) {
Expand Down Expand Up @@ -364,7 +366,7 @@ HRESULT getCellInfo(HWND hwnd, IDispatch* pDispatchRange, long cellInfoFlags, EX
}
}
if(cellInfoFlags&NVCELLINFOFLAG_STATES) {
cellInfo->states=getCellStates(hwnd,pDispatchRange);
cellInfo->nvCellStates = getCellStates(hwnd, pDispatchRange);
}
CComPtr<IDispatch> pDispatchMergeArea=nullptr;
if(cellInfoFlags&NVCELLINFOFLAG_COORDS||cellInfoFlags&NVCELLINFOFLAG_OUTLINELEVEL) {
Expand Down
44 changes: 26 additions & 18 deletions nvdaHelper/remote/excel/Constants.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
This file is a part of the NVDA project.
Copyright 2019-2020 NV Access Limited, Accessolutions, Julien Cochuyt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Copyright 2019-2022 NV Access Limited, Accessolutions, Julien Cochuyt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This license can be found at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
Expand Down Expand Up @@ -78,17 +78,25 @@ const long NVCELLINFOFLAG_COMMENTS=0x40;
const long NVCELLINFOFLAG_FORMULA=0x80;
const long NVCELLINFOFLAG_ALL=0xffff;

// NVDA states
const __int64 NVSTATE_EXPANDED=0x100;
const __int64 NVSTATE_COLLAPSED=0x200;
const __int64 NVSTATE_LINKED=0x1000;
const __int64 NVSTATE_HASPOPUP=0x2000;
const __int64 NVSTATE_PROTECTED=0x4000;
const __int64 NVSTATE_HASFORMULA=0x1000000000;
const __int64 NVSTATE_HASCOMMENT=0x2000000000;
const __int64 NVSTATE_CROPPED=0x8000000000;
const __int64 NVSTATE_OVERFLOWING=0x10000000000;
const __int64 NVSTATE_UNLOCKED=0x20000000000;
constexpr std::uint64_t setBit(const unsigned int bitPos) {
return std::uint64_t(1) << bitPos;
}

/*NVDA sell specific states.
These values must match NvCellState enum in source/nvdaObjects/excel.py
*/
enum NvCellState : std::uint64_t {
EXPANDED = setBit(1),
COLLAPSED = setBit(2),
LINKED = setBit(3),
HASPOPUP = setBit(4),
PROTECTED = setBit(5),
HASFORMULA = setBit(6),
HASCOMMENT = setBit(7),
CROPPED = setBit(8),
OVERFLOWING = setBit(9),
UNLOCKED = setBit(10)
};

// an HRESULT error code randomly given by Excel such as for validation.type when there is no validation on the cell
const HRESULT XLGeneralError=0x800a03ec;
53 changes: 47 additions & 6 deletions source/NVDAObjects/window/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

import abc
import ctypes
import enum
from typing import (
Optional, Dict,
)

from comtypes import COMError, BSTR
import comtypes.automation
import wx
Expand Down Expand Up @@ -1059,13 +1064,41 @@ def _get_locationText(self):
NVCELLINFOFLAG_FORMULA=0x80
NVCELLINFOFLAG_ALL=0xffff


class NvCellState(enum.IntEnum):
EXPANDED = 1 << 1,
COLLAPSED = 1 << 2,
LINKED = 1 << 3,
HASPOPUP = 1 << 4,
PROTECTED = 1 << 5,
HASFORMULA = 1 << 6,
HASCOMMENT = 1 << 7,
CROPPED = 1 << 8,
OVERFLOWING = 1 << 9,
UNLOCKED = 1 << 10,


_nvCellStatesToStates: Dict[NvCellState, controlTypes.State] = {
NvCellState.EXPANDED: controlTypes.State.EXPANDED,
NvCellState.COLLAPSED: controlTypes.State.COLLAPSED,
NvCellState.LINKED: controlTypes.State.LINKED,
NvCellState.HASPOPUP: controlTypes.State.HASPOPUP,
NvCellState.PROTECTED: controlTypes.State.PROTECTED,
NvCellState.HASFORMULA: controlTypes.State.HASFORMULA,
NvCellState.HASCOMMENT: controlTypes.State.HASCOMMENT,
NvCellState.CROPPED: controlTypes.State.CROPPED,
NvCellState.OVERFLOWING: controlTypes.State.OVERFLOWING,
NvCellState.UNLOCKED: controlTypes.State.UNLOCKED,
}


class ExcelCellInfo(ctypes.Structure):
_fields_=[
('text',comtypes.BSTR),
('address',comtypes.BSTR),
('inputTitle',comtypes.BSTR),
('inputMessage',comtypes.BSTR),
('states',ctypes.c_longlong),
('nvCellStates', ctypes.c_longlong), # bitwise OR of the NvCellState enum values.
('rowNumber',ctypes.c_long),
('rowSpan',ctypes.c_long),
('columnNumber',ctypes.c_long),
Expand All @@ -1075,6 +1108,7 @@ class ExcelCellInfo(ctypes.Structure):
('formula',comtypes.BSTR),
]


class ExcelCellInfoQuickNavItem(browseMode.QuickNavItem):

def __init__( self , parentIterator, cellInfo):
Expand Down Expand Up @@ -1181,7 +1215,10 @@ def collectionFromWorksheet(self,worksheetObject):

class ExcelCell(ExcelBase):

def _get_excelCellInfo(self):
excelCellInfo: Optional[ExcelCellInfo]
"""Type info for auto property: _get_excelCellInfo"""

def _get_excelCellInfo(self) -> Optional[ExcelCellInfo]:
if not self.appModule.helperLocalBindingHandle:
return None
ci=ExcelCellInfo()
Expand Down Expand Up @@ -1382,10 +1419,14 @@ def _get_states(self):
cellInfo=self.excelCellInfo
if not cellInfo:
return states
stateBits=cellInfo.states
for state in controlTypes.State:
if stateBits & state.value:
states.add(state)
nvCellStates = cellInfo.nvCellStates

for possibleCellState in NvCellState:
if nvCellStates & possibleCellState.value:
states.add(
# intentionally use indexing operator so an error is raised for a missing key
_nvCellStatesToStates[possibleCellState]
)
return states

def event_typedCharacter(self,ch):
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2022 NV Access Limited

"""Unit tests for the excel module.
"""

import unittest
import NVDAObjects.window.excel as excel


class TestCellStates(unittest.TestCase):
def test_cellStateMapsToState(self):
"""All Excel Cell info states should map to a controlTypes.State
"""
for cellState in excel.NvCellState:
excel._nvCellStatesToStates[cellState] # throws if cellState is missing