Skip to content

Commit

Permalink
Microsoft Word support: add support for reporting older style form fi…
Browse files Browse the repository at this point in the history
…elds in word documents. Re #2295.

Checkboxes, text input and combo box controls are supported so far. Their existance is announced within reading text, including their role, and state for checkboxes and value for combo boxes. The status text is also included as an inforced name.
Neither bookmarks, or moveEnd(story) was used. Rather instead we expand to paragraph and use the first formField found where the original range is found to be inRange of that formField's range.
  • Loading branch information
michaelDCurran committed Aug 8, 2013
1 parent f1106cb commit 8d01115
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 30 deletions.
114 changes: 85 additions & 29 deletions nvdaHelper/remote/winword.cpp
Expand Up @@ -35,6 +35,7 @@ using namespace std;
#define wdDISPID_SELECTION_RANGE 400
#define wdDISPID_SELECTION_SETRANGE 100
#define wdDISPID_SELECTION_STARTISACTIVE 404
#define wdDISPID_RANGE_INRANGE 126
#define wdDISPID_RANGE_STORYTYPE 7
#define wdDISPID_RANGE_MOVEEND 111
#define wdDISPID_RANGE_COLLAPSE 101
Expand All @@ -47,6 +48,13 @@ using namespace std;
#define wdDISPID_RANGE_INFORMATION 313
#define wdDISPID_RANGE_STYLE 151
#define wdDISPID_RANGE_LANGUAGEID 153
#define wdDISPID_RANGE_DUPLICATE 6
#define wdDISPID_RANGE_FORMFIELDS 65
#define wdDISPID_FORMFIELDS_ITEM 0
#define wdDISPID_FORMFIELD_RANGE 17
#define wdDISPID_FORMFIELD_TYPE 0
#define wdDISPID_FORMFIELD_RESULT 10
#define wdDISPID_FORMFIELD_STATUSTEXT 8
#define wdDISPID_STYLE_NAMELOCAL 0
#define wdDISPID_RANGE_SPELLINGERRORS 316
#define wdDISPID_SPELLINGERRORS_COUNT 1
Expand Down Expand Up @@ -102,6 +110,7 @@ using namespace std;

#define wdCharacter 1
#define wdWord 2
#define wdParagraph 4
#define wdLine 5
#define wdCharacterFormatting 13

Expand Down Expand Up @@ -192,6 +201,45 @@ void winword_expandToLine_helper(HWND hwnd, winword_expandToLine_args* args) {
_com_dispatch_raw_propput(pDispatchApplication,wdDISPID_APPLICATION_SCREENUPDATING,VT_BOOL,true);
}

BOOL generateFormFieldXML(IDispatch* pDispatchRange, wostringstream& XMLStream, int& chunkEnd) {
IDispatchPtr pDispatchRange2=NULL;
if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_DUPLICATE,VT_DISPATCH,&pDispatchRange2)!=S_OK||!pDispatchRange2) {
return false;
}
_com_dispatch_raw_method(pDispatchRange2,wdDISPID_RANGE_EXPAND,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdParagraph,1);
IDispatchPtr pDispatchFormFields=NULL;
if(_com_dispatch_raw_propget(pDispatchRange2,wdDISPID_RANGE_FORMFIELDS,VT_DISPATCH,&pDispatchFormFields)!=S_OK||!pDispatchFormFields) {
return false;
}
BOOL foundFormField=false;
for(int count=1;!foundFormField&&count<100;++count) {
IDispatchPtr pDispatchFormField=NULL;
if(_com_dispatch_raw_method(pDispatchFormFields,wdDISPID_FORMFIELDS_ITEM,DISPATCH_METHOD,VT_DISPATCH,&pDispatchFormField,L"\x0003",count)!=S_OK||!pDispatchFormField) {
break;
}
IDispatchPtr pDispatchFormFieldRange=NULL;
if(_com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_RANGE,VT_DISPATCH,&pDispatchFormFieldRange)!=S_OK||!pDispatchFormFieldRange) {
break;
}
if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_INRANGE,DISPATCH_METHOD,VT_BOOL,&foundFormField,L"\x0009",pDispatchFormFieldRange)!=S_OK||!foundFormField) {
continue;
}
long fieldType=-1;
_com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_TYPE,VT_I4,&fieldType);
BSTR fieldResult=NULL;
_com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_RESULT,VT_BSTR,&fieldResult);
BSTR fieldStatusText=NULL;
_com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_STATUSTEXT,VT_BSTR,&fieldStatusText);
XMLStream<<L"<control wdFieldType=\""<<fieldType<<L"\" wdFieldResult=\""<<(fieldResult?fieldResult:L"")<<L"\" wdFieldStatusText=\""<<(fieldStatusText?fieldStatusText:L"")<<L"\">";
if(fieldResult) SysFreeString(fieldResult);
if(fieldStatusText) SysFreeString(fieldStatusText);
_com_dispatch_raw_propget(pDispatchFormFieldRange,wdDISPID_RANGE_END,VT_I4,&chunkEnd);
_com_dispatch_raw_propput(pDispatchRange,wdDISPID_RANGE_END,VT_I4,chunkEnd);
break;
}
return foundFormField;
}

int generateHeadingXML(IDispatch* pDispatchRange, wostringstream& XMLStream) {
IDispatchPtr pDispatchParagraphs=NULL;
IDispatchPtr pDispatchParagraph=NULL;
Expand Down Expand Up @@ -544,15 +592,27 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args)
int chunkEndOffset=chunkStartOffset;
int unitsMoved=0;
BSTR text=NULL;
if(initialFormatConfig&formatConfig_reportTables) {
neededClosingControlTagCount+=generateTableXML(pDispatchRange,args->startOffset,args->endOffset,XMLStream);
}
if(initialFormatConfig&formatConfig_reportHeadings) {
neededClosingControlTagCount+=generateHeadingXML(pDispatchRange,XMLStream);
}
generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,initialFormatConfig,initialFormatAttribsStream);
bool firstLoop=true;
//Walk the range from the given start to end by characterFormatting or word units
//And grab any text and formatting and generate appropriate xml
bool firstLoop=true;
do {
//Move the end by word
if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdWord,1)!=S_OK||unitsMoved<=0) {
break;
//generated form field xml if in a form field
//Also automatically extends the range and chunkEndOffset to the end of the field
BOOL isFormField=generateFormFieldXML(pDispatchRange,XMLStream,chunkEndOffset);
if(!isFormField) {
//Move the end by word
if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdWord,1)!=S_OK||unitsMoved<=0) {
break;
}
_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset);
}
_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset);
//Make sure that the end is not past the requested end after the move
if(chunkEndOffset>(args->endOffset)) {
_com_dispatch_raw_propput(pDispatchRange,wdDISPID_RANGE_END,VT_I4,args->endOffset);
Expand All @@ -564,34 +624,29 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args)
break;
}
_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_TEXT,VT_BSTR,&text);
if(!text) SysAllocString(L"");
if(text) {
//Force a new chunk before and after control+b (note characters)
int noteCharOffset=-1;
for(int i=0;text[i]!=L'\0';++i) {
if(text[i]==L'\x0002') {
noteCharOffset=i;
if(i==0) text[i]=L' ';
break;
}
}
bool isNoteChar=(noteCharOffset==0);
if(noteCharOffset==0) noteCharOffset=1;
if(noteCharOffset>0) {
text[noteCharOffset]=L'\0';
_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_COLLAPSE,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdCollapseStart);
if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdCharacter,noteCharOffset)!=S_OK||unitsMoved<=0) {
break;
}
_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset);
}
if(firstLoop) {
if(initialFormatConfig&formatConfig_reportTables) {
neededClosingControlTagCount+=generateTableXML(pDispatchRange,args->startOffset,args->endOffset,XMLStream);
bool isNoteChar=false;
if(!isFormField) {
//Force a new chunk before and after control+b (note characters)
for(int i=0;text[i]!=L'\0';++i) {
if(text[i]==L'\x0002') {
noteCharOffset=i;
if(i==0) text[i]=L' ';
break;
}
}
if(initialFormatConfig&formatConfig_reportHeadings) {
neededClosingControlTagCount+=generateHeadingXML(pDispatchRange,XMLStream);
isNoteChar=(noteCharOffset==0);
if(noteCharOffset==0) noteCharOffset=1;
if(noteCharOffset>0) {
text[noteCharOffset]=L'\0';
_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_COLLAPSE,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdCollapseStart);
if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdCharacter,noteCharOffset)!=S_OK||unitsMoved<=0) {
break;
}
_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset);
}
generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,initialFormatConfig,initialFormatAttribsStream);
}
if(isNoteChar) {
isNoteChar=generateFootnoteEndnoteXML(pDispatchRange,XMLStream,true);
Expand Down Expand Up @@ -625,6 +680,7 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args)
SysFreeString(text);
text=NULL;
XMLStream<<L"</text>";
if(isFormField) XMLStream<<L"</control>";
if(isNoteChar) XMLStream<<L"</control>";
if(inlineShapesCount>0) XMLStream<<L"</control>";
}
Expand Down
21 changes: 20 additions & 1 deletion source/NVDAObjects/window/winword.py
Expand Up @@ -81,6 +81,10 @@
wdPrimaryHeaderStory=7
wdTextFrameStory=5

wdFieldFormTextInput=70
wdFieldFormCheckBox=71
wdFieldFormDropDown=83

storyTypeLocalizedLabels={
wdCommentsStory:_("Comments"),
wdEndnotesStory:_("Endnotes"),
Expand All @@ -94,6 +98,12 @@
wdTextFrameStory:_("Text frame"),
}

wdFieldTypesToNVDARoles={
wdFieldFormTextInput:controlTypes.ROLE_EDITABLETEXT,
wdFieldFormCheckBox:controlTypes.ROLE_CHECKBOX,
wdFieldFormDropDown:controlTypes.ROLE_COMBOBOX,
}

winwordWindowIid=GUID('{00020962-0000-0000-C000-000000000046}')

wm_winword_expandToLine=ctypes.windll.user32.RegisterWindowMessageW(u"wm_winword_expandToLine")
Expand Down Expand Up @@ -226,7 +236,16 @@ def _normalizeControlField(self,field):
elif role=="object":
role=controlTypes.ROLE_EMBEDDEDOBJECT
else:
role=controlTypes.ROLE_UNKNOWN
fieldType=int(field.pop('wdFieldType',-1))
role=wdFieldTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN)
if fieldType==wdFieldFormCheckBox and int(field.get('wdFieldResult','0'))>0:
field['states']=set([controlTypes.STATE_CHECKED])
elif fieldType==wdFieldFormDropDown:
field['value']=field.get('wdFieldResult',None)
fieldStatusText=field.pop('wdFieldStatusText',None)
if fieldStatusText:
field['name']=fieldStatusText
field['alwaysReportName']=True
field['role']=role
storyType=int(field.pop('wdStoryType',0))
if storyType:
Expand Down

0 comments on commit 8d01115

Please sign in to comment.