HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::GetText(int maxLength, BSTR *text) { if (text == NULL) return E_POINTER; if (!document->IsDocumentLoaded()) return E_FAIL; if (IsNullRange() || IsEmptyRange()) { *text = SysAllocString(L""); // 0-sized not-null string return S_OK; } TextSelection selection(document->GetDM()->engine(), document->GetDM()->textCache); selection.StartAt(startPage, startGlyph); selection.SelectUpTo(endPage, endGlyph); ScopedMem<WCHAR> selected_text(selection.ExtractText(L"\r\n")); size_t selected_text_length = str::Len(selected_text); // -1 and [0, inf) are allowed if (maxLength > -2) { if (maxLength != -1 && selected_text_length > (size_t)maxLength) selected_text[maxLength] = '\0'; // truncate *text = SysAllocString(selected_text); if (*text) return S_OK; else return E_OUTOFMEMORY; } else { return E_INVALIDARG; } }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::ExpandToEnclosingUnit(enum TextUnit textUnit) { //if document is closed, don't do anything if (!document->IsDocumentLoaded()) return E_FAIL; //if not set, don't do anything if (IsNullRange()) return S_OK; if (textUnit == TextUnit_Character) { //done return S_OK; } else if (textUnit == TextUnit_Format) { // what is a "format" anyway? return S_OK; } else if (textUnit == TextUnit_Word) { // select current word at start endpoint int word_beg = FindPreviousWordEndpoint(startPage, startGlyph); int word_end = FindNextWordEndpoint(startPage, startGlyph); endPage = startPage; startGlyph = word_beg; endGlyph = word_end; return S_OK; } else if (textUnit == TextUnit_Line || textUnit == TextUnit_Paragraph) { // select current line or current paragraph. In general case these cannot be differentiated? Right? int word_beg = FindPreviousLineEndpoint(startPage, startGlyph); int word_end = FindNextLineEndpoint(startPage, startGlyph); endPage = startPage; startGlyph = word_beg; endGlyph = word_end; return S_OK; } else if (textUnit == TextUnit_Page) { // select current page // start from the beginning of start page startGlyph = 0; // to the end of the end page endGlyph = GetPageGlyphCount(endPage); return S_OK; } else if (textUnit == TextUnit_Document) { SetToDocumentRange(); return S_OK; } else { return E_INVALIDARG; } }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::Select(void) { if (!document->IsDocumentLoaded()) return E_FAIL; if (IsNullRange() || IsEmptyRange()) { document->GetDM()->textSelection->Reset(); } else { document->GetDM()->textSelection->Reset(); document->GetDM()->textSelection->StartAt(startPage, startGlyph); document->GetDM()->textSelection->SelectUpTo(endPage, endGlyph); } return S_OK; }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::GetBoundingRectangles(SAFEARRAY** boundingRects) { if (boundingRects == nullptr) return E_POINTER; if (!document->IsDocumentLoaded()) return E_FAIL; if (IsNullRange()) { SAFEARRAY* sarray = SafeArrayCreateVector(VT_R8, 0, 0); if (!sarray) return E_OUTOFMEMORY; *boundingRects = sarray; return S_OK; } // TODO: support GetBoundingRectangles return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::ScrollIntoView(BOOL alignToTop) { if (!document->IsDocumentLoaded()) return E_FAIL; // extract target location int target_page, target_idx; if (IsNullRange()) { target_page = 0; target_idx = 0; } else if (alignToTop) { target_page = startPage; target_idx = startGlyph; } else { target_page = endPage; target_idx = endGlyph; } // TODO: Scroll to target_page, target_idx // document->GetDM()->ScrollYTo() return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::GetChildren(SAFEARRAY **children) { if (children == NULL) return E_POINTER; if (!document->IsDocumentLoaded()) return E_FAIL; // return all children in range if (IsNullRange()) { SAFEARRAY *psa = SafeArrayCreateVector(VT_UNKNOWN, 0, 0); if (!psa) return E_OUTOFMEMORY; *children = psa; return S_OK; } SAFEARRAY *psa = SafeArrayCreateVector(VT_UNKNOWN, 0, endPage - startPage + 1); if (!psa) return E_OUTOFMEMORY; SumatraUIAutomationPageProvider* it = document->GetFirstPage(); while (it) { if (it->GetPageNum() >= startPage || it->GetPageNum() <= endPage) { LONG index = it->GetPageNum() - startPage; HRESULT hr = SafeArrayPutElement(psa, &index, it); CrashIf(FAILED(hr)); it->AddRef(); } it = it->GetNextPage(); } *children = psa; return S_OK; }
HRESULT STDMETHODCALLTYPE SumatraUIAutomationTextRange::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int *moved) { if (moved == NULL) return E_POINTER; // if document is closed, don't do anything if (!document->IsDocumentLoaded()) return E_FAIL; // if not set, don't do anything if (IsNullRange()) return S_OK; // what to move int *target_page, *target_glyph; if (endpoint == TextPatternRangeEndpoint_Start) { target_page = &startPage; target_glyph = &startGlyph; } else if (endpoint == TextPatternRangeEndpoint_End) { target_page = &endPage; target_glyph = &endGlyph; } else { return E_INVALIDARG; } class EndPointMover { protected: SumatraUIAutomationTextRange* target; int* target_page; int* target_glyph; public: // return false when cannot be moved virtual bool NextEndpoint() const { // HACK: Declaring these as pure virtual causes "unreferenced local variable" warnings ==> define a dummy body to get rid of warnings CrashIf(true); return false; } virtual bool PrevEndpoint() const { CrashIf(true); return false; } // return false when not appliable bool NextPage() const { int max_glyph = target->GetPageGlyphCount(*target_page); if (*target_glyph == max_glyph) { if (*target_page == target->GetPageCount()) { // last page return false; } // go to next page (*target_page)++; (*target_glyph) = 0; } return true; } bool PreviousPage() const { if (*target_glyph == 0) { if (*target_page == 1) { // first page return false; } // go to next page (*target_page)--; (*target_glyph) = target->GetPageGlyphCount(*target_page); } return true; } // do the moving int Move(int count, SumatraUIAutomationTextRange* target, int* target_page, int* target_glyph) { this->target = target; this->target_page = target_page; this->target_glyph = target_glyph; int retVal = 0; if (count > 0) { for (int i=0;i<count && (NextPage() || NextEndpoint());++i) ++retVal; } else { for (int i=0;i<-count && (PreviousPage() || PrevEndpoint());++i) ++retVal; } return retVal; } }; class CharEndPointMover : public EndPointMover { bool NextEndpoint() const { (*target_glyph)++; return true; } bool PrevEndpoint() const { (*target_glyph)--; return true; } }; class WordEndPointMover : public EndPointMover { bool NextEndpoint() const { (*target_glyph) = target->FindNextWordEndpoint(*target_page, *target_glyph, true); return true; } bool PrevEndpoint() const { (*target_glyph) = target->FindPreviousWordEndpoint(*target_page, *target_glyph, true); (*target_glyph)--; return true; } }; class LineEndPointMover : public EndPointMover { bool NextEndpoint() const { (*target_glyph) = target->FindNextLineEndpoint(*target_page, *target_glyph, true); return true; } bool PrevEndpoint() const { (*target_glyph) = target->FindPreviousLineEndpoint(*target_page, *target_glyph, true); (*target_glyph)--; return true; } }; // how much to move if (unit == TextUnit_Character) { CharEndPointMover mover; *moved = mover.Move(count, this, target_page, target_glyph); } else if (unit == TextUnit_Word || unit == TextUnit_Format) { WordEndPointMover mover; *moved = mover.Move(count, this, target_page, target_glyph); } else if (unit == TextUnit_Line || unit == TextUnit_Paragraph) { LineEndPointMover mover; *moved = mover.Move(count, this, target_page, target_glyph); } else if (unit == TextUnit_Page) { *moved = 0; *target_glyph = 0; if (count > 0) { // GetPageCount()+1 => allow overflow momentarily for (int i=0;i<count && *target_page!=GetPageCount()+1;++i) { (*target_page)++; (*moved)++; } // fix overflow, allow seeking to the end this way if (*target_page == GetPageCount()+1) { *target_page = GetPageCount(); *target_glyph = GetPageGlyphCount(*target_page); } } else { for (int i=0;i<-count && *target_page!=1;++i) { (*target_page)--; (*moved)++; } } } else if (unit == TextUnit_Document) { if (count > 0) { int end_page = GetPageCount(); int end_glyph = GetPageGlyphCount(*target_page); if (*target_page != end_page || *target_glyph != end_glyph) { *target_page = end_page; *target_glyph = end_glyph; *moved = 1; } else { *moved = 0; } } else { const int beg_page = 0; const int beg_glyph = 0; if (*target_page != beg_page || *target_glyph != beg_glyph) { *target_page = beg_page; *target_glyph = beg_glyph; *moved = 1; } else { *moved = 0; } } } else { return E_INVALIDARG; } // keep range valid if (endpoint == TextPatternRangeEndpoint_Start) { // drag end with start ValidateEndEndpoint(); } else if (endpoint == TextPatternRangeEndpoint_End) { // drag start with end ValidateStartEndpoint(); } return S_OK; }