2025-11-28 00:35:46 +09:00

1518 lines
43 KiB
C++

// Copyright (c) Microsoft Corporation
//
#include <windows.h>
#include <ole2.h>
#include <uiautomation.h>
#include <strsafe.h>
#include <wchar.h>
#include "AnnotatedTextControl.h"
#include "FrameProvider.h"
#include "AnnotationProvider.h"
#include "TextAreaProvider.h"
const TEXTATTRIBUTEID lineVariableAttributes[] =
{
UIA_FontSizeAttributeId,
UIA_FontWeightAttributeId,
UIA_IndentationFirstLineAttributeId,
UIA_IndentationLeadingAttributeId,
UIA_IsItalicAttributeId,
UIA_UnderlineStyleAttributeId,
UIA_StyleNameAttributeId,
UIA_StyleIdAttributeId
};
const TEXTATTRIBUTEID charVariableAttributes[] =
{
UIA_AnnotationTypesAttributeId,
UIA_AnnotationObjectsAttributeId
};
void NotifyCaretPositionChanged(_In_ HWND hwnd, _In_ AnnotatedTextControl *control)
{
TextAreaProvider *eventControl = new TextAreaProvider(hwnd, control);
if (eventControl == NULL)
{
// This is an error, but there's not good place to report it in this sample,
// so just proceed
}
else
{
UiaRaiseAutomationEvent(eventControl, UIA_Text_TextSelectionChangedEventId);
eventControl->Release();
}
}
TextAreaProvider::TextAreaProvider(_In_ HWND hwnd, _In_ AnnotatedTextControl *control)
: _refCount(1)
{
_hwnd = hwnd;
_control = control;
}
TextAreaProvider::~TextAreaProvider()
{
}
ULONG STDMETHODCALLTYPE TextAreaProvider::AddRef()
{
return ++_refCount;
}
ULONG STDMETHODCALLTYPE TextAreaProvider::Release()
{
if (--_refCount <= 0)
{
delete this;
return 0;
}
return _refCount;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppInterface)
{
if (riid == __uuidof(IUnknown)) *ppInterface =(IUnknown*)((IRawElementProviderSimple*)this);
else if (riid == __uuidof(IRawElementProviderSimple)) *ppInterface =(IRawElementProviderSimple*)this;
else if (riid == __uuidof(IRawElementProviderFragment)) *ppInterface =(IRawElementProviderFragment*)this;
else if (riid == __uuidof(ITextProvider)) *ppInterface =(ITextProvider*)this;
else if (riid == __uuidof(ITextProvider2)) *ppInterface =(ITextProvider2*)this;
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
((IUnknown*)(*ppInterface))->AddRef();
return S_OK;
}
// IRawElementProviderSimple Implementation
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_ProviderOptions(_Out_ ProviderOptions * retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetPatternProvider(PATTERNID patternId, _Outptr_result_maybenull_ IUnknown ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = NULL;
if (patternId == UIA_TextPatternId || patternId == UIA_TextPattern2Id)
{
*retVal = static_cast<ITextProvider2 *>(this);
(*retVal)->AddRef();
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetPropertyValue(PROPERTYID idProp, _Out_ VARIANT * retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
retVal->vt = VT_EMPTY;
// Returning the default will leave the property as the default
// so we only really need to touch it for the properties we want to implement
if (idProp == UIA_ControlTypePropertyId)
{
// This control is the Document control type, implying that it is
// a complex document that supports text pattern
retVal->vt = VT_I4;
retVal->lVal = UIA_DocumentControlTypeId;
}
else if (idProp == UIA_NamePropertyId)
{
// In a production application, this would be localized text.
retVal->bstrVal = SysAllocString(L"Text Area");
if (retVal->bstrVal != NULL)
{
retVal->vt = VT_BSTR;
}
}
else if (idProp == UIA_AutomationIdPropertyId)
{
retVal->bstrVal = SysAllocString(L"Text Area");
if (retVal->bstrVal != NULL)
{
retVal->vt = VT_BSTR;
}
}
else if (idProp == UIA_IsControlElementPropertyId)
{
retVal->vt = VT_BOOL;
retVal->boolVal = VARIANT_TRUE;
}
else if (idProp == UIA_IsContentElementPropertyId)
{
retVal->vt = VT_BOOL;
retVal->boolVal = VARIANT_TRUE;
}
else if (idProp == UIA_IsKeyboardFocusablePropertyId)
{
retVal->vt = VT_BOOL;
retVal->boolVal = VARIANT_FALSE;
}
else if (idProp == UIA_HasKeyboardFocusPropertyId)
{
retVal->vt = VT_BOOL;
retVal->boolVal = VARIANT_FALSE;
}
else if (idProp == UIA_ProviderDescriptionPropertyId)
{
retVal->bstrVal = SysAllocString(L"Microsoft Sample: Uia Document Content Text Area");
if (retVal->bstrVal != NULL)
{
retVal->vt = VT_BSTR;
}
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_HostRawElementProvider(_Outptr_result_maybenull_ IRawElementProviderSimple ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = NULL;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::Navigate(NavigateDirection direction, _Outptr_result_maybenull_ IRawElementProviderFragment ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = NULL;
HRESULT hr = S_OK;
if (direction == NavigateDirection_Parent)
{
*retVal = new FrameProvider(_hwnd, _control);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
}
else if (direction == NavigateDirection_NextSibling && _control->GetAnnotationCount() > 0)
{
*retVal = new AnnotationProvider(_hwnd, _control, 0);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
}
// For the other directions (firstchild, lastchild, previous) the default of NULL is correct
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// AppendRuntimeId is a magic Number that tells UIAutomation to Append its own Runtime ID(From the HWND)
int rId[] = { UiaAppendRuntimeId, -1 };
// BuildIntSafeArray is a custom function to hide the SafeArray creation
HRESULT hr = S_OK;
*retVal = BuildIntSafeArray(rId, 2);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_BoundingRectangle(_Out_ UiaRect * retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
RECT rc;
if (!GetWindowRect(_hwnd, &rc))
{
return HRESULT_FROM_WIN32(GetLastError());
}
retVal->left = rc.left;
retVal->top = rc.top;
retVal->width = _control->GetTextAreaWidth();
retVal->height = rc.bottom - rc.top;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = NULL;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::SetFocus()
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_FragmentRoot(_Outptr_result_maybenull_ IRawElementProviderFragmentRoot ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = new FrameProvider(_hwnd, _control);
return (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
// TextProvider methods
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetSelection(_Outptr_result_maybenull_ SAFEARRAY** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// The control does not support selection
*retVal = NULL;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
RECT winRect;
if (!GetClientRect(_hwnd, &winRect))
{
return HRESULT_FROM_WIN32(GetLastError());
}
EndPoint start = _control->SearchForClosestEndPoint(static_cast<float>(winRect.left), static_cast<float>(winRect.top));
EndPoint end = _control->SearchForClosestEndPoint(static_cast<float>(winRect.right), static_cast<float>(winRect.bottom));
Range visibleRange = {start, end};
HRESULT hr = S_OK;
ITextRangeProvider *visibleRangeProvider = new TextAreaTextRange(_hwnd, _control, visibleRange);
if (visibleRangeProvider == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
*retVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
long index = 0;
hr = SafeArrayPutElement(*retVal, &index, visibleRangeProvider);
if (FAILED(hr))
{
SafeArrayDestroy(*retVal);
*retVal = NULL;
}
}
visibleRangeProvider->Release();
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::RangeFromChild(_In_opt_ IRawElementProviderSimple * childElement, _Outptr_result_maybenull_ ITextRangeProvider ** retVal)
{
UNREFERENCED_PARAMETER(childElement);
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// There are no children of this text control
*retVal = NULL;
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::RangeFromPoint(UiaPoint screenLocation, _Outptr_result_maybenull_ ITextRangeProvider ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
POINT screenPt = {static_cast<int>(screenLocation.x), static_cast<int>(screenLocation.y)};
MapWindowPoints(NULL, _hwnd, &screenPt, 1);
EndPoint closest = _control->SearchForClosestEndPoint(static_cast<float>(screenPt.x), static_cast<float>(screenPt.y));
Range closestRange = {closest, closest};
*retVal = new TextAreaTextRange(_hwnd, _control, closestRange);
return (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_DocumentRange(_Outptr_result_maybenull_ ITextRangeProvider ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
Range full = {{0, 0}, _control->GetEnd()};
*retVal = new TextAreaTextRange(_hwnd, _control, full);
return (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::get_SupportedTextSelection(_Out_ SupportedTextSelection * retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// The control does not support selection
*retVal = SupportedTextSelection_None;
return S_OK;
}
// ITextProvider2 methods
HRESULT STDMETHODCALLTYPE TextAreaProvider::RangeFromAnnotation(_In_opt_ IRawElementProviderSimple *annotationElement, _Outptr_result_maybenull_ ITextRangeProvider **retVal)
{
*retVal = NULL;
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
if (annotationElement == NULL)
{
return E_INVALIDARG;
}
AnnotationProvider *annotationInternal;
// Can't use IID_PPV_ARGS because of IUnknown ambiguity
if (FAILED(annotationElement->QueryInterface(__uuidof(AnnotationProvider), reinterpret_cast<void **>(&annotationInternal))))
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
HWND hwnd = annotationInternal->GetControlHwnd();
int annotationId = annotationInternal->GetAnnotationId();
annotationInternal->Release();
// Tried to input an annotation from a different instance of the control
if (hwnd != _hwnd)
{
hr = E_INVALIDARG;
}
Annotation *annotation = NULL;
if (SUCCEEDED(hr))
{
annotation = _control->GetAnnotation(annotationId);
// Points to a non-existant annotation
if (annotation == NULL)
{
hr = E_INVALIDARG;
}
}
if (SUCCEEDED(hr))
{
Range annotationRange = {{annotation->line, annotation->start_char}, {annotation->line, annotation->start_char + annotation->length}};
int lineLength = _control->GetLineLength(annotation->line);
if (annotation->length == -1 || annotationRange.end.character > lineLength)
{
annotationRange.end.character = lineLength;
}
*retVal = new TextAreaTextRange(_hwnd, _control, annotationRange);
hr = (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaProvider::GetCaretRange(_Out_ BOOL *isActive, _Outptr_result_maybenull_ ITextRangeProvider **retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*isActive = static_cast<BOOL>(_control->IsActive());
EndPoint caret = _control->GetCaretPosition();
Range caretRange = {caret, caret};
*retVal = new TextAreaTextRange(_hwnd, _control, caretRange);
return (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
TextAreaTextRange::TextAreaTextRange(_In_ HWND hwnd, _In_ AnnotatedTextControl *control, _In_ Range range)
: _refCount(1)
{
_hwnd = hwnd;
_control = control;
_range = range;
}
TextAreaTextRange::~TextAreaTextRange()
{
}
// IUnknown methods
ULONG STDMETHODCALLTYPE TextAreaTextRange::AddRef()
{
return ++_refCount;
}
ULONG STDMETHODCALLTYPE TextAreaTextRange::Release()
{
if (--_refCount <= 0)
{
delete this;
return 0;
}
return _refCount;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppInterface)
{
if (riid == __uuidof(IUnknown)) *ppInterface =(IUnknown*)((ITextRangeProvider*)this);
else if (riid == __uuidof(ITextRangeProvider)) *ppInterface =(ITextRangeProvider*)this;
else if (riid == __uuidof(TextAreaTextRange)) *ppInterface =this;
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
((IUnknown*)(*ppInterface))->AddRef();
return S_OK;
}
// ITextRangeProvider methods
HRESULT STDMETHODCALLTYPE TextAreaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider ** retVal)
{
HRESULT hr = S_OK;
*retVal = new TextAreaTextRange(_hwnd, _control, _range);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::Compare(_In_opt_ ITextRangeProvider * range, _Out_ BOOL *retVal)
{
*retVal = FALSE;
if (range != NULL)
{
TextAreaTextRange *rangeInternal;
if (SUCCEEDED(range->QueryInterface(IID_PPV_ARGS(&rangeInternal))))
{
if (_hwnd == rangeInternal->_hwnd &&
QuickCompareEndpoints(rangeInternal->_range.begin, _range.begin) == 0 &&
QuickCompareEndpoints(rangeInternal->_range.end, _range.end) == 0)
{
*retVal = TRUE;
}
rangeInternal->Release();
}
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::CompareEndpoints(TextPatternRangeEndpoint endpoint, _In_opt_ ITextRangeProvider *targetRange, _In_ TextPatternRangeEndpoint targetEndpoint, _Out_ int *retVal)
{
if (targetRange == NULL)
{
return E_INVALIDARG;
}
TextAreaTextRange *rangeInternal;
if (FAILED(targetRange->QueryInterface(IID_PPV_ARGS(&rangeInternal))))
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
if (_hwnd != rangeInternal->_hwnd)
{
hr = E_INVALIDARG;
}
else
{
Range target = rangeInternal->_range;
EndPoint thisEnd = (endpoint == TextPatternRangeEndpoint_Start) ? _range.begin : _range.end;
EndPoint targetEnd = (targetEndpoint == TextPatternRangeEndpoint_Start) ? target.begin : target.end;
*retVal = QuickCompareEndpoints(thisEnd, targetEnd);
}
rangeInternal->Release();
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::ExpandToEnclosingUnit(_In_ TextUnit unit)
{
// This control supports the units: TextUnit_Character, TextUnit_Format, TextUnit_Word, TextUnit_Paragraph, TextUnit_Document
// This control does not support: TextUnit_Line, TextUnit_Page
HRESULT hr = S_OK;
if (unit == TextUnit_Character)
{
_range.end = _range.begin;
_range.end.character++;
if (_range.end.character > _control->GetLineLength(_range.begin.line))
{
if (_range.end.line + 1 >= _control->GetLineCount())
{
_range.end = _range.begin;
_range.begin.character--;
}
else
{
_range.end.line = _range.begin.line + 1;
_range.end.character = 0;
}
}
}
else if (unit == TextUnit_Format || unit == TextUnit_Word)
{
int walked;
_range.begin = Walk(_range.begin, false, unit, 0, 0, &walked);
_range.end = Walk(_range.begin, true, unit, 0, 1, &walked);
if ( walked < 1 )
{
_range.begin = Walk(_range.end, false, unit, 0, 1, &walked);
}
}
else if (unit == TextUnit_Line || unit == TextUnit_Paragraph)
{
// As stated above, this sample does not support line-by-line navigation. It does support paragraph-by-paragraph
// navigation, and in that case, check if the starting position of the current range is already at the end of a
// paragraph. If it is, move to the next paragraph.
if ((unit == TextUnit_Paragraph) && (_range.begin.character == _control->GetLineLength(_range.begin.line)))
{
_range.begin.line++;
}
_range.begin.character = 0;
_range.end.line = _range.begin.line;
_range.end.character = _control->GetLineLength(_range.end.line);
}
else if (unit == TextUnit_Page || unit == TextUnit_Document)
{
_range.begin.character = 0;
_range.begin.line = 0;
_range.end = _control->GetEnd();
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::FindAttribute(_In_ TEXTATTRIBUTEID textAttributeId, _In_ VARIANT val, _In_ BOOL searchBackward, _Outptr_result_maybenull_ ITextRangeProvider **retVal)
{
HRESULT hr = S_OK;
*retVal = NULL;
EndPoint start = searchBackward ? _range.end : _range.begin;
EndPoint finish = searchBackward ? _range.begin : _range.end;
EndPoint current = start;
// This will loop until 'current' passes or is equal to the end
while (QuickCompareEndpoints(searchBackward ? finish : current, searchBackward ? current : finish) < 0)
{
int walked;
EndPoint next = Walk(current, !searchBackward, TextUnit_Format, textAttributeId, 1, &walked);
VARIANT curValue = _control->GetAttributeAtPoint(searchBackward ? current : next, textAttributeId);
hr = VarCmp(&val, &curValue, LOCALE_NEUTRAL);
if (hr == VARCMP_EQ)
{
Range found;
found.begin = searchBackward ? next : current;
// For line based attributes, the start character will be the end of the previous line
// bump it up one to make it an even line
for (int i = 0; i < ARRAYSIZE(lineVariableAttributes); i++)
{
if (textAttributeId == lineVariableAttributes[i])
{
if (found.begin.character > 0)
{
found.begin.character = 0;
found.begin.line++;
}
break;
}
}
// If next is past the end of the current range, end at the end of the current range instead
if (QuickCompareEndpoints(searchBackward ? finish : next, searchBackward ? next : finish) > 0)
{
found.end = finish;
}
else
{
found.end = searchBackward ? current : next;
}
*retVal = new TextAreaTextRange(_hwnd, _control, found);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
break;
}
current = next;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::FindText(_In_ BSTR text, BOOL searchBackward, BOOL ignoreCase, _Outptr_result_maybenull_ ITextRangeProvider **retVal)
{
UNREFERENCED_PARAMETER(text);
UNREFERENCED_PARAMETER(searchBackward);
UNREFERENCED_PARAMETER(ignoreCase);
// The FindText operation is not implemented in this sample.
*retVal = NULL;
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT *retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
HRESULT hr = S_OK;
VariantInit(retVal);
int walked;
EndPoint endOfAttribute = Walk(_range.begin, true, TextUnit_Format, textAttributeId, 1, &walked);
if (QuickCompareEndpoints(endOfAttribute, _range.end) < 0)
{
hr = UiaGetReservedMixedAttributeValue(&retVal->punkVal);
if (SUCCEEDED(hr))
{
retVal->vt = VT_UNKNOWN;
}
}
else
{
if ( textAttributeId == UIA_AnnotationTypesAttributeId ||
textAttributeId == UIA_AnnotationObjectsAttributeId )
{
int *annotationIds;
int annotationCount;
if (!GetAnnotationsAtPoint(_range.begin, &annotationIds, &annotationCount))
{
hr = E_OUTOFMEMORY;
}
else
{
if (textAttributeId == UIA_AnnotationTypesAttributeId)
{
int *annotationTypes = new int[annotationCount];
if (annotationTypes == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
for (int i = 0; i < annotationCount; i++)
{
annotationTypes[i] = AnnotationType_Comment;
}
retVal->parray = BuildIntSafeArray(annotationTypes, annotationCount);
if (retVal->parray == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
retVal->vt = VT_I4|VT_ARRAY;
}
delete [] annotationTypes;
}
}
else if (textAttributeId == UIA_AnnotationObjectsAttributeId)
{
retVal->parray = SafeArrayCreateVector(VT_UNKNOWN, 0, annotationCount);
if (retVal->parray == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
for (long i = 0; SUCCEEDED(hr) && i < annotationCount; i++)
{
IRawElementProviderSimple *provider = new AnnotationProvider(_hwnd, _control, annotationIds[i]);
if (provider == NULL || FAILED(SafeArrayPutElement(retVal->parray, &i, provider)))
{
SafeArrayDestroy(retVal->parray);
retVal->parray = NULL;
hr = E_OUTOFMEMORY;
}
if (provider != NULL)
{
provider->Release();
}
if (FAILED(hr))
{
break;
}
}
}
if (SUCCEEDED(hr))
{
retVal->vt = VT_UNKNOWN|VT_ARRAY;
}
}
delete [] annotationIds;
}
}
else
{
*retVal = _control->GetAttributeAtPoint(_range.begin, textAttributeId);
}
}
return hr;
}
HRESULT TextAreaTextRange::SafeArrayInsertRect(_In_ SAFEARRAY * sa, _In_ RectF rect, _In_ long index)
{
HRESULT hr = S_OK;
RECT rc = {static_cast<int>(rect.X),
static_cast<int>(rect.Y),
static_cast<int>(rect.GetRight()),
static_cast<int>(rect.GetBottom())};
// Convert from Client coordinates to desktop coordinates
MapWindowPoints(_hwnd, NULL, reinterpret_cast<POINT *>(&rc), 2);
double data[4];
data[0] = rc.left;
data[1] = rc.top;
data[2] = rc.right - rc.left;
data[3] = rc.bottom - rc.top;
for (int i = 0; SUCCEEDED(hr) && i < 4; i++)
{
hr = SafeArrayPutElement(sa, &index,(void *)&(data[i]));
index++;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
HRESULT hr = S_OK;
*retVal = NULL;
int numLines = _range.end.line - _range.begin.line + 1;
Region ** lineRegions = new Region*[numLines];
if (lineRegions == NULL)
{
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
for (int i = 0; i < numLines; i++)
{
lineRegions[i] = NULL;
}
Graphics graphics(_hwnd);
Matrix identity;
EndPoint current = _range.begin;
int rectCount = 0;
for (int i = 0; i < numLines; i++)
{
if (QuickCompareEndpoints(current, _range.end) >= 0)
{
break;
}
if (current.line < _range.end.line)
{
int length = _control->GetLineLength(current.line);
// Add 1 for the implied newline
int getLength = length - current.character;
lineRegions[i] = _control->GetLineCharactersPosition(current.line, current.character, getLength, &graphics);
current.line++;
current.character = 0;
}
else
{
int getLength = _range.end.character - current.character;
lineRegions[i] = _control->GetLineCharactersPosition(current.line, current.character, getLength, &graphics);
current.character = _range.end.character;
}
if (lineRegions[i] != NULL)
{
rectCount += lineRegions[i]->GetRegionScansCount(&identity);
}
}
if (rectCount > 0)
{
RectF *boundingRects = new RectF[rectCount];
if (boundingRects != NULL)
{
int currentRect = 0;
for (int i = 0; i < numLines; i++)
{
if (lineRegions[i] != NULL)
{
int count;
if (lineRegions[i]->GetRegionScans(&identity, &boundingRects[currentRect], &count) == Ok && count > 0)
{
currentRect += count;
}
}
}
if (currentRect == rectCount)
{
*retVal = SafeArrayCreateVector(VT_R8, 0, rectCount * 4);
if (*retVal != NULL)
{
for (int i = 0; i < rectCount; i++)
{
if (FAILED(SafeArrayInsertRect(*retVal, boundingRects[i], i * 4)))
{
SafeArrayDestroy(*retVal);
*retVal = NULL;
break;
}
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
delete [] boundingRects;
}
else
{
hr = E_OUTOFMEMORY;
}
}
// Cleanup
for (int i = 0; i < numLines; i++)
{
if (lineRegions[i] != NULL)
{
delete lineRegions[i];
lineRegions[i] = NULL;
}
}
delete [] lineRegions;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple **retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = new TextAreaProvider(_hwnd, _control);
return (*retVal == NULL) ? E_OUTOFMEMORY : S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::GetText(_In_ int maxLength, _Out_ BSTR * retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
HRESULT hr = S_OK;
int builderSize;
PWSTR textBuilder;
if (maxLength >= 0)
{
builderSize = maxLength + 1;
}
else
{
builderSize = 0;
EndPoint current = _range.begin;
while (QuickCompareEndpoints(current, _range.end) < 0)
{
if (current.line < _range.end.line)
{
int length = _control->GetLineLength(current.line);
// Add 1 for the implied newline
builderSize += length - current.character + 1;
current.line++;
current.character = 0;
}
else
{
builderSize += _range.end.character - current.character;
current.character = _range.end.character;
}
}
// Always allow space for the final string terminator.
builderSize++;
}
textBuilder = new WCHAR[builderSize];
if (textBuilder != NULL)
{
int writePos = 0;
EndPoint current = _range.begin;
while (QuickCompareEndpoints(current, _range.end) < 0 && writePos < (builderSize - 1))
{
int copyLength = 0;
EndPoint next;
bool trailingNewline = false;
if (current.line < _range.end.line)
{
int length = _control->GetLineLength(current.line);
// Add 1 for the implied newline
copyLength = length - current.character;
trailingNewline = true;
next.line = current.line + 1;
next.character = 0;
}
else
{
copyLength = _range.end.character - current.character;
next.line = current.line;
next.character = _range.end.character;
}
if (writePos + copyLength >= (builderSize - 1))
{
copyLength = (builderSize - 1) - writePos;
}
TextLine *textLine = _control->GetLine(current.line);
StringCchCopyN(&textBuilder[writePos], copyLength + 1, &textLine->text[current.character], copyLength);
writePos += copyLength;
current = next;
if (trailingNewline && writePos < (builderSize - 1))
{
textBuilder[writePos++] = '\n';
}
}
// Ensure the string is null-terminated
textBuilder[writePos] = '\0';
*retVal = SysAllocString(textBuilder);
if (*retVal == NULL)
{
hr = E_OUTOFMEMORY;
}
delete [] textBuilder;
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::Move(_In_ TextUnit unit, _In_ int count, _Out_ int *retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = 0;
bool isDegenerate = (QuickCompareEndpoints(_range.begin, _range.end) == 0);
int walked;
EndPoint back = Walk(_range.begin, false, unit, 0, 0, &walked);
EndPoint destination = Walk(back, count > 0, unit, 0, abs(count), retVal);
_range.begin = destination;
_range.end = destination;
if (!isDegenerate)
{
return ExpandToEnclosingUnit(unit);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, _In_ TextUnit unit, _In_ int count, _Out_ int *retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
*retVal = 0;
if (endpoint == TextPatternRangeEndpoint_Start)
{
_range.begin = Walk(_range.begin, count > 0, unit, 0, abs(count), retVal);
if (QuickCompareEndpoints(_range.begin, _range.end) > 0)
{
_range.end = _range.begin;
}
}
else
{
_range.end = Walk(_range.end, count > 0, unit, 0, abs(count), retVal);
if (QuickCompareEndpoints(_range.begin, _range.end) > 0)
{
_range.begin = _range.end;
}
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, _In_opt_ ITextRangeProvider *targetRange, _In_ TextPatternRangeEndpoint targetEndpoint)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
if (targetRange == NULL)
{
return E_INVALIDARG;
}
TextAreaTextRange *rangeInternal;
if (FAILED(targetRange->QueryInterface(IID_PPV_ARGS(&rangeInternal))))
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
if (_hwnd != rangeInternal->_hwnd)
{
hr = E_INVALIDARG;
}
else
{
EndPoint src;
if (targetEndpoint == TextPatternRangeEndpoint_Start)
{
src = rangeInternal->_range.begin;
}
else
{
src = rangeInternal->_range.end;
}
if (endpoint == TextPatternRangeEndpoint_Start)
{
_range.begin = src;
if (QuickCompareEndpoints(_range.begin, _range.end) > 0)
{
_range.end = _range.begin;
}
}
else
{
_range.end = src;
if (QuickCompareEndpoints(_range.begin, _range.end) > 0)
{
_range.begin = _range.end;
}
}
}
rangeInternal->Release();
return hr;
}
// This control does not support selection
HRESULT STDMETHODCALLTYPE TextAreaTextRange::Select()
{
return UIA_E_INVALIDOPERATION;
}
// This control does not support selection
HRESULT STDMETHODCALLTYPE TextAreaTextRange::AddToSelection()
{
return UIA_E_INVALIDOPERATION;
}
// This control does not support selection
HRESULT STDMETHODCALLTYPE TextAreaTextRange::RemoveFromSelection()
{
return UIA_E_INVALIDOPERATION;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::ScrollIntoView(_In_ BOOL alignToTop)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// The ScrollInView operation is not implemented in this sample
UNREFERENCED_PARAMETER(alignToTop);
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE TextAreaTextRange::GetChildren(_Outptr_result_maybenull_ SAFEARRAY ** retVal)
{
if (!IsWindow(_hwnd))
{
return UIA_E_ELEMENTNOTAVAILABLE;
}
// No children
*retVal = NULL;
return S_OK;
}
bool TextAreaTextRange::CheckEndPointIsUnitEndpoint(_In_ EndPoint check, _In_ TextUnit unit, _In_ TEXTATTRIBUTEID specificAttribute)
{
if (unit == TextUnit_Character)
{
return true;
}
EndPoint next;
EndPoint prev;
if (!_control->StepCharacter(check, true, &next) ||
!_control->StepCharacter(check, false, &prev))
{
// If we're at the beginning or end, we're at an endpoint
return true;
}
else if (unit == TextUnit_Word)
{
if (IsWhiteSpace(prev) && !IsWhiteSpace(check))
{
return true;
}
return false;
}
else if (unit == TextUnit_Line || unit == TextUnit_Paragraph)
{
return check.line != next.line;
}
// TextUnit_Page and TextUnit_Document are covered by the initial beginning/end check
else if (unit == TextUnit_Page || unit == TextUnit_Document)
{
return false;
}
else if (unit == TextUnit_Format)
{
bool matching = true;
bool checkedLineBoundary = false;
// There are limited attributes that vary in this control
// If its not one of those attributes, then it is not an Endpoint
// unless it's the document start or end, which is checked above
for (int i = 0; i < ARRAYSIZE(lineVariableAttributes); i++)
{
if (specificAttribute == 0 || specificAttribute == lineVariableAttributes[i])
{
if (!checkedLineBoundary)
{
if (!CheckEndPointIsUnitEndpoint(check, TextUnit_Paragraph, 0))
{
break;
}
checkedLineBoundary = true;
}
VARIANT varC = _control->GetAttributeAtPoint(check, lineVariableAttributes[i]);
VARIANT varN = _control->GetAttributeAtPoint(next, lineVariableAttributes[i]);
HRESULT hr = VarCmp(&varC, &varN, LOCALE_NEUTRAL);
VariantClear(&varC);
VariantClear(&varN);
if (hr != VARCMP_EQ)
{
matching = false;
break;
}
}
}
for (int i = 0; i < ARRAYSIZE(charVariableAttributes); i++)
{
if (specificAttribute == 0 || specificAttribute == charVariableAttributes[i])
{
int *annotationIds;
int annotationCount;
if (GetAnnotationsAtPoint(check, &annotationIds, &annotationCount))
{
int *prevAnnotationIds;
int prevAnnotationCount;
if (GetAnnotationsAtPoint(prev, &prevAnnotationIds, &prevAnnotationCount))
{
if (annotationCount != prevAnnotationCount)
{
matching = false;
}
// Since all our annotations are the same type, if the number matches,
// then the UIA_AnnotationTypesAttributeId all match
else if (charVariableAttributes[i] == UIA_AnnotationObjectsAttributeId)
{
for (int j = 0; j < annotationCount; j++)
{
if (annotationIds[j] != prevAnnotationIds[j])
{
matching = false;
}
}
}
delete [] prevAnnotationIds;
}
delete [] annotationIds;
}
}
}
return !matching;
}
return false;
}
EndPoint TextAreaTextRange::Walk(_In_ EndPoint start, _In_ bool forward, _In_ TextUnit unit, _In_ TEXTATTRIBUTEID specificAttribute, _In_ int count, _Out_ int *walked)
{
*walked = 0;
// Use count of zero to normalize
if (count == 0)
{
if (CheckEndPointIsUnitEndpoint(start, unit, specificAttribute))
{
return start;
}
count = 1;
}
TextUnit walkUnit = unit;
if (unit == TextUnit_Word)
{
walkUnit = TextUnit_Character;
}
if (unit == TextUnit_Line)
{
walkUnit = TextUnit_Paragraph;
}
if (unit == TextUnit_Page)
{
walkUnit = TextUnit_Document;
}
if (unit == TextUnit_Format)
{
walkUnit = TextUnit_Character;
for (int i = 0; i < ARRAYSIZE(lineVariableAttributes); i++)
{
if (specificAttribute == lineVariableAttributes[i])
{
walkUnit = TextUnit_Paragraph;
break;
}
}
}
EndPoint current = start;
for (int i = 0; i < count; i++)
{
EndPoint checkNext;
if (!_control->StepCharacter(current, forward, &checkNext))
{
// If we're at the beginning or end... so stop now and return
break;
}
do
{
if (walkUnit == TextUnit_Character)
{
EndPoint next;
if (_control->StepCharacter(current, forward, &next))
{
current = next;
}
}
else if (walkUnit == TextUnit_Paragraph)
{
EndPoint next;
if (_control->StepLine(current, forward, &next))
{
current = next;
}
}
else if (walkUnit == TextUnit_Document)
{
if (forward)
{
current = _control->GetEnd();
}
else
{
current.line = 0;
current.character = 0;
}
}
}
while (!CheckEndPointIsUnitEndpoint(current, unit, specificAttribute));
(*walked)++;
}
// When walking backwards, clients expect negative values for distance walked
if (!forward)
{
*walked = -*walked;
}
return current;
}
bool TextAreaTextRange::GetAnnotationsAtPoint(_In_ EndPoint endPoint, _Outptr_result_buffer_(*annotationCount) int **annotationIds, _Out_ int *annotationCount)
{
*annotationCount = 0;
*annotationIds = NULL;
unsigned int totalCount = _control->GetAnnotationCount();
unsigned int atPointCount = 0;
for (unsigned int i = 0; i < totalCount; i++)
{
Annotation *annotation = _control->GetAnnotation(i);
if (annotation != NULL)
{
if (annotation->line == endPoint.line &&
endPoint.character >= annotation->start_char &&
(endPoint.character < annotation->start_char + annotation->length ||
annotation->length == -1))
{
atPointCount++;
}
}
}
*annotationIds = new int[atPointCount];
if (*annotationIds == NULL)
{
return false;
}
for (unsigned int i = 0; i < totalCount && (*annotationCount) < (int)atPointCount; i++)
{
Annotation *annotation = _control->GetAnnotation(i);
if (annotation != NULL)
{
if (annotation->line == endPoint.line &&
endPoint.character >= annotation->start_char &&
(endPoint.character < annotation->start_char + annotation->length ||
annotation->length == -1))
{
(*annotationIds)[*annotationCount] = i;
(*annotationCount)++;
}
}
}
return true;
}
bool TextAreaTextRange::IsWhiteSpace(_In_ EndPoint check)
{
if (check.character >= _control->GetLineLength(check.line))
{
return true;
}
TextLine *line = _control->GetLine(check.line);
int isSpace = iswspace(line->text[check.character]);
return isSpace != 0;
}