/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>
#include <sal/log.hxx>

#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/i18n/Boundary.hpp>
#include <cppuhelper/exc_hlp.hxx>
#include <extended/textwindowaccessibility.hxx>
#include <comphelper/accessiblecontexthelper.hxx>
#include <comphelper/accessibleeventnotifier.hxx>
#include <o3tl/safeint.hxx>
#include <unotools/accessiblerelationsethelper.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/txtattr.hxx>
#include <vcl/unohelp.hxx>
#include <vcl/window.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/sequence.hxx>

#include <algorithm>
#include <memory>
#include <numeric>
#include <vector>

namespace accessibility
{
void SfxListenerGuard::startListening(::SfxBroadcaster & rNotifier)
{
    assert(m_pNotifier == nullptr && "called more than once");
    m_pNotifier = &rNotifier;
    m_rListener.StartListening(*m_pNotifier, DuplicateHandling::Prevent);
}

void SfxListenerGuard::endListening()
{
    if (m_pNotifier != nullptr)
    {
        m_rListener.EndListening(*m_pNotifier);
        m_pNotifier = nullptr;
    }
}

void WindowListenerGuard::startListening(vcl::Window & rNotifier)
{
    assert(m_pNotifier == nullptr && "called more than once");
    m_pNotifier = &rNotifier;
    m_pNotifier->AddEventListener(m_aListener);
}

void WindowListenerGuard::endListening()
{
    if (m_pNotifier)
    {
        m_pNotifier->RemoveEventListener(m_aListener);
        m_pNotifier = nullptr;
    }
}

Paragraph::Paragraph(::rtl::Reference< Document > xDocument,
                             Paragraphs::size_type nNumber):
    ParagraphBase(m_aMutex),
    m_xDocument(std::move(xDocument)),
    m_nNumber(nNumber),
    m_nClientId(0)
{
    m_aParagraphText = m_xDocument->retrieveParagraphText(this);
}

void
Paragraph::numberChanged(bool bIncremented)
{
    if (bIncremented)
        ++m_nNumber;
    else
        --m_nNumber;
}

void Paragraph::textChanged()
{
    OUString aParagraphText = implGetText();
    css::uno::Any aOldValue, aNewValue;
    if ( implInitTextChangedEvent( m_aParagraphText, aParagraphText, aOldValue, aNewValue ) )
    {
        m_aParagraphText = aParagraphText;
        notifyEvent(css::accessibility::AccessibleEventId::
                    TEXT_CHANGED,
                    aOldValue, aNewValue);
    }
}

void Paragraph::notifyEvent(::sal_Int16 nEventId,
                                css::uno::Any const & rOldValue,
                                css::uno::Any const & rNewValue)
{
    if (m_nClientId)
        comphelper::AccessibleEventNotifier::addEvent( m_nClientId, css::accessibility::AccessibleEventObject(
                             getXWeak(),
                             nEventId, rNewValue, rOldValue, -1) );
}

// virtual
css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL
Paragraph::getAccessibleContext()
{
    checkDisposed();
    return this;
}

// virtual
sal_Int64 SAL_CALL Paragraph::getAccessibleChildCount()
{
    checkDisposed();
    return 0;
}

// virtual
css::uno::Reference< css::accessibility::XAccessible > SAL_CALL
Paragraph::getAccessibleChild(sal_Int64)
{
    checkDisposed();
    throw css::lang::IndexOutOfBoundsException(
        u"textwindowaccessibility.cxx:"
        " Paragraph::getAccessibleChild"_ustr,
        getXWeak());
}

// virtual
css::uno::Reference< css::accessibility::XAccessible > SAL_CALL
Paragraph::getAccessibleParent()
{
    checkDisposed();
    return m_xDocument->getAccessible();
}

// virtual
sal_Int64 SAL_CALL Paragraph::getAccessibleIndexInParent()
{
    checkDisposed();
    return m_xDocument->retrieveParagraphIndex(this);
}

// virtual
::sal_Int16 SAL_CALL Paragraph::getAccessibleRole()
{
    checkDisposed();
    return css::accessibility::AccessibleRole::PARAGRAPH;
}

// virtual
OUString SAL_CALL Paragraph::getAccessibleDescription()
{
    checkDisposed();
    return OUString();
}

// virtual
OUString SAL_CALL Paragraph::getAccessibleName()
{
    checkDisposed();
    return OUString();
}

// virtual
css::uno::Reference< css::accessibility::XAccessibleRelationSet >
SAL_CALL Paragraph::getAccessibleRelationSet()
{
    checkDisposed();
    return m_xDocument->retrieveParagraphRelationSet( this );
}

// virtual
sal_Int64 SAL_CALL Paragraph::getAccessibleStateSet()
{
    checkDisposed();

    // FIXME  Notification of changes (STATE_CHANGED) missing when
    // m_rView.IsReadOnly() changes:
    return m_xDocument->retrieveParagraphState(this);
}

// virtual
css::lang::Locale SAL_CALL Paragraph::getLocale()
{
    checkDisposed();
    return m_xDocument->retrieveLocale();
}

// virtual
sal_Bool SAL_CALL Paragraph::containsPoint(css::awt::Point const & rPoint)
{
    checkDisposed();
    css::awt::Rectangle aRect(m_xDocument->retrieveParagraphBounds(this,
                                                                     false));
    return rPoint.X >= 0 && rPoint.X < aRect.Width
        && rPoint.Y >= 0 && rPoint.Y < aRect.Height;
}

// virtual
css::uno::Reference< css::accessibility::XAccessible > SAL_CALL
Paragraph::getAccessibleAtPoint(css::awt::Point const &)
{
    checkDisposed();
    return nullptr;
}

// virtual
css::awt::Rectangle SAL_CALL Paragraph::getBounds()
{
    checkDisposed();
    return m_xDocument->retrieveParagraphBounds(this, false);
}

// virtual
css::awt::Point SAL_CALL Paragraph::getLocation()
{
    checkDisposed();
    css::awt::Rectangle aRect(m_xDocument->retrieveParagraphBounds(this,
                                                                     false));
    return css::awt::Point(aRect.X, aRect.Y);
}

// virtual
css::awt::Point SAL_CALL Paragraph::getLocationOnScreen()
{
    checkDisposed();
    css::awt::Rectangle aRect(m_xDocument->retrieveParagraphBounds(this,
                                                                     true));
    return css::awt::Point(aRect.X, aRect.Y);
}

// virtual
css::awt::Size SAL_CALL Paragraph::getSize()
{
    checkDisposed();
    css::awt::Rectangle aRect(m_xDocument->retrieveParagraphBounds(this,
                                                                     false));
    return css::awt::Size(aRect.Width, aRect.Height);
}

// virtual
void SAL_CALL Paragraph::grabFocus()
{
    checkDisposed();
    VclPtr<vcl::Window> pWindow = m_xDocument->GetWindow();
    if ( pWindow )
    {
        pWindow->GrabFocus();
    }
    try
    {
        m_xDocument->changeParagraphSelection(this, 0, 0);
    }
    catch (const css::lang::IndexOutOfBoundsException &)
    {
        TOOLS_INFO_EXCEPTION("accessibility", "Paragraph::grabFocus: caught unexpected");
    }
}

// virtual
sal_Int32 SAL_CALL Paragraph::getForeground()
{
    return 0; // TODO
}

// virtual
sal_Int32 SAL_CALL Paragraph::getBackground()
{
    return 0; // TODO
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getCaretPosition()
{
    checkDisposed();
    return m_xDocument->retrieveParagraphCaretPosition(this);
}

// virtual
sal_Bool SAL_CALL Paragraph::setCaretPosition(::sal_Int32 nIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphSelection(this, nIndex, nIndex);
    return true;
}

// virtual
::sal_Unicode SAL_CALL Paragraph::getCharacter(::sal_Int32 nIndex)
{
    checkDisposed();
    return OCommonAccessibleText::implGetCharacter(implGetText(), nIndex);
}

// virtual
css::uno::Sequence< css::beans::PropertyValue > SAL_CALL
Paragraph::getCharacterAttributes(::sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes)
{
    checkDisposed();
    return m_xDocument->retrieveCharacterAttributes( this, nIndex, aRequestedAttributes );
}

// virtual
css::awt::Rectangle SAL_CALL
Paragraph::getCharacterBounds(::sal_Int32 nIndex)
{
    checkDisposed();
    css::awt::Rectangle aBounds(m_xDocument->retrieveCharacterBounds(this, nIndex));
    css::awt::Rectangle aParaBounds(m_xDocument->retrieveParagraphBounds(this, false));
    aBounds.X -= aParaBounds.X;
    aBounds.Y -= aParaBounds.Y;
    return aBounds;
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getCharacterCount()
{
    checkDisposed();
    return implGetText().getLength();
}

// virtual
::sal_Int32 SAL_CALL
Paragraph::getIndexAtPoint(css::awt::Point const & rPoint)
{
    checkDisposed();
    css::awt::Point aPoint(rPoint);
    css::awt::Rectangle aParaBounds(m_xDocument->retrieveParagraphBounds(this, false));
    aPoint.X += aParaBounds.X;
    aPoint.Y += aParaBounds.Y;
    return m_xDocument->retrieveCharacterIndex(this, aPoint);
}

// virtual
OUString SAL_CALL Paragraph::getSelectedText()
{
    checkDisposed();

    return OCommonAccessibleText::getSelectedText();
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getSelectionStart()
{
    checkDisposed();
    return OCommonAccessibleText::getSelectionStart();
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getSelectionEnd()
{
    checkDisposed();
    return OCommonAccessibleText::getSelectionEnd();
}

// virtual
sal_Bool SAL_CALL Paragraph::setSelection(::sal_Int32 nStartIndex,
                                                ::sal_Int32 nEndIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphSelection(this, nStartIndex, nEndIndex);
    return true;
}

// virtual
OUString SAL_CALL Paragraph::getText()
{
    checkDisposed();
    return implGetText();
}

// virtual
OUString SAL_CALL Paragraph::getTextRange(::sal_Int32 nStartIndex,
                                                     ::sal_Int32 nEndIndex)
{
    checkDisposed();
    return OCommonAccessibleText::implGetTextRange(implGetText(), nStartIndex, nEndIndex);
}

// virtual
css::accessibility::TextSegment SAL_CALL Paragraph::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
    checkDisposed();
    return OCommonAccessibleText::getTextAtIndex(nIndex, aTextType);
}

// virtual
css::accessibility::TextSegment SAL_CALL Paragraph::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
    checkDisposed();
    return OCommonAccessibleText::getTextBeforeIndex(nIndex, aTextType);
}

// virtual
css::accessibility::TextSegment SAL_CALL Paragraph::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
    checkDisposed();
    return OCommonAccessibleText::getTextBehindIndex(nIndex, aTextType);
}

// virtual
sal_Bool SAL_CALL Paragraph::copyText(::sal_Int32 nStartIndex,
                                            ::sal_Int32 nEndIndex)
{
    checkDisposed();
    m_xDocument->copyParagraphText(this, nStartIndex, nEndIndex);
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::scrollSubstringTo( sal_Int32, sal_Int32, css::accessibility::AccessibleScrollType )
{
    return false;
}

// virtual
sal_Bool SAL_CALL Paragraph::cutText(::sal_Int32 nStartIndex,
                                           ::sal_Int32 nEndIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, nStartIndex, nEndIndex, true, false,
                                     OUString());
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::pasteText(::sal_Int32 nIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, nIndex, nIndex, false, true,
                                     OUString());
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::deleteText(::sal_Int32 nStartIndex,
                                          ::sal_Int32 nEndIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, nStartIndex, nEndIndex, false, false,
                                     OUString());
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::insertText(OUString const & rText,
                                              ::sal_Int32 nIndex)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, nIndex, nIndex, false, false, rText);
    return true;
}

// virtual
sal_Bool SAL_CALL
Paragraph::replaceText(::sal_Int32 nStartIndex, ::sal_Int32 nEndIndex,
                           OUString const & rReplacement)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, nStartIndex, nEndIndex, false, false,
                                     rReplacement);
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::setAttributes(
    ::sal_Int32 nStartIndex, ::sal_Int32 nEndIndex,
    css::uno::Sequence< css::beans::PropertyValue > const & rAttributeSet)
{
    checkDisposed();
    m_xDocument->changeParagraphAttributes(this, nStartIndex, nEndIndex,
                                           rAttributeSet);
    return true;
}

// virtual
sal_Bool SAL_CALL Paragraph::setText(OUString const & rText)
{
    checkDisposed();
    m_xDocument->changeParagraphText(this, rText);
    return true;
}

// virtual
css::uno::Sequence< css::beans::PropertyValue > SAL_CALL
Paragraph::getDefaultAttributes(const css::uno::Sequence< OUString >&)
{
    checkDisposed();
    return {}; // default attributes are not supported by text engine
}

// virtual
css::uno::Sequence< css::beans::PropertyValue > SAL_CALL
Paragraph::getRunAttributes(::sal_Int32 Index, const css::uno::Sequence< OUString >& RequestedAttributes)
{
    checkDisposed();
    return m_xDocument->retrieveRunAttributes( this, Index, RequestedAttributes );
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getLineNumberAtIndex( ::sal_Int32 nIndex )
{
    checkDisposed();

    ::sal_Int32 nLineNo = -1;
    m_xDocument->retrieveParagraphLineBoundary( this, nIndex, &nLineNo );

    return nLineNo;
}

// virtual
css::accessibility::TextSegment SAL_CALL Paragraph::getTextAtLineNumber( ::sal_Int32 nLineNo )
{
    checkDisposed();

    css::i18n::Boundary aBoundary =
        m_xDocument->retrieveParagraphBoundaryOfLine( this, nLineNo );

    return css::accessibility::TextSegment( getTextRange(aBoundary.startPos, aBoundary.endPos),
        aBoundary.startPos, aBoundary.endPos);
}

// virtual
css::accessibility::TextSegment SAL_CALL Paragraph::getTextAtLineWithCaret(  )
{
    checkDisposed();

    sal_Int32 nLineNo = getNumberOfLineWithCaret();

    try {
        return ( nLineNo >= 0 ) ?
            getTextAtLineNumber( nLineNo ) :
            css::accessibility::TextSegment();
    } catch (const css::lang::IndexOutOfBoundsException&) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(
            u"textwindowaccessibility.cxx:"
            " Paragraph::getTextAtLineWithCaret"_ustr,
            getXWeak(), anyEx );
    }
}

// virtual
::sal_Int32 SAL_CALL Paragraph::getNumberOfLineWithCaret(  )
{
    checkDisposed();
    return m_xDocument->retrieveParagraphLineWithCursor(this);
}


// virtual
void SAL_CALL Paragraph::addAccessibleEventListener(
    css::uno::Reference<
    css::accessibility::XAccessibleEventListener > const & rListener)
{
    if (!rListener.is())
        return;

    ::osl::ClearableMutexGuard aGuard(rBHelper.rMutex);
    if (rBHelper.bDisposed || rBHelper.bInDispose)
    {
        aGuard.clear();
        rListener->disposing(css::lang::EventObject(
                                getXWeak()));
    }
    else
    {
        if (!m_nClientId)
            m_nClientId = comphelper::AccessibleEventNotifier::registerClient( );
        comphelper::AccessibleEventNotifier::addEventListener( m_nClientId, rListener );
    }
}

// virtual
void SAL_CALL Paragraph::removeAccessibleEventListener(
    css::uno::Reference<
    css::accessibility::XAccessibleEventListener > const & rListener)
{
    comphelper::AccessibleEventNotifier::TClientId nId = 0;
    {
        osl::MutexGuard aGuard(rBHelper.rMutex);
        if (rListener.is() && m_nClientId != 0
            && comphelper::AccessibleEventNotifier::removeEventListener( m_nClientId, rListener ) == 0)
        {
            nId = m_nClientId;
            m_nClientId = 0;
        }
    }
    if (nId != 0)
    {
        // no listeners anymore
        // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
        // and at least to us not firing any events anymore, in case somebody calls
        // NotifyAccessibleEvent, again
        comphelper::AccessibleEventNotifier::revokeClient(nId);
    }
}

// virtual
void SAL_CALL Paragraph::disposing()
{
    comphelper::AccessibleEventNotifier::TClientId nId = 0;
    {
        osl::MutexGuard aGuard(rBHelper.rMutex);
        nId = m_nClientId;
        m_nClientId = 0;
    }
    if (nId != 0)
        comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing(nId, *this);
}

// virtual
OUString Paragraph::implGetText()
{
    return m_xDocument->retrieveParagraphText(this);
}

// virtual
css::lang::Locale Paragraph::implGetLocale()
{
    return m_xDocument->retrieveLocale();
}

// virtual
void Paragraph::implGetSelection(::sal_Int32 & rStartIndex,
                                     ::sal_Int32 & rEndIndex)
{
    m_xDocument->retrieveParagraphSelection(this, &rStartIndex, &rEndIndex);
}

// virtual
void Paragraph::implGetParagraphBoundary( const OUString& rText,
                                          css::i18n::Boundary& rBoundary,
                                          ::sal_Int32 nIndex )
{
    ::sal_Int32 nLength = rText.getLength();

    if ( implIsValidIndex( nIndex, nLength ) )
    {
        rBoundary.startPos = 0;
        rBoundary.endPos = nLength;
    }
    else
    {
        rBoundary.startPos = nIndex;
        rBoundary.endPos = nIndex;
    }
}

// virtual
void Paragraph::implGetLineBoundary( const OUString& rText,
                                     css::i18n::Boundary& rBoundary,
                                     ::sal_Int32 nIndex )
{
    ::sal_Int32 nLength = rText.getLength();

    if ( implIsValidIndex( nIndex, nLength ) || nIndex == nLength )
    {
        css::i18n::Boundary aBoundary =
            m_xDocument->retrieveParagraphLineBoundary( this, nIndex, nullptr );
        rBoundary.startPos = aBoundary.startPos;
        rBoundary.endPos = aBoundary.endPos;
    }
    else
    {
        rBoundary.startPos = nIndex;
        rBoundary.endPos = nIndex;
    }
}


void Paragraph::checkDisposed()
{
    ::osl::MutexGuard aGuard(rBHelper.rMutex);
    if (!(rBHelper.bDisposed || rBHelper.bInDispose))
        return;
    throw css::lang::DisposedException(
        OUString(), getXWeak());
}

Document::Document(::VCLXWindow * pVclXWindow, ::TextEngine & rEngine,
                   ::TextView & rView):
    VCLXAccessibleComponent(pVclXWindow),
    m_xAccessible(pVclXWindow),
    m_rEngine(rEngine),
    m_rView(rView),
    m_aEngineListener(*this),
    m_aViewListener(LINK(this, Document, WindowEventHandler)),
    m_nVisibleBeginOffset(0),
    m_nSelectionFirstPara(-1),
    m_nSelectionFirstPos(-1),
    m_nSelectionLastPara(-1),
    m_nSelectionLastPos(-1),
    m_bSelectionChangedNotification(false)
{
    const sal_uInt32 nCount = m_rEngine.GetParagraphCount();
    m_aParagraphs.reserve(nCount);
    for (sal_uInt32 i = 0; i < nCount; ++i)
        m_aParagraphs.emplace_back(m_rEngine.GetTextHeight(i));
    m_nViewOffset = static_cast<::sal_Int32>(m_rView.GetStartDocPos().Y()); // XXX  numeric overflow
    m_nViewHeight = static_cast<::sal_Int32>(m_rView.GetWindow()->GetOutputSizePixel().Height());
    // XXX  numeric overflow
    determineVisibleRange();
    m_nFocused = m_aParagraphs.size();
    m_aEngineListener.startListening(m_rEngine);
    m_aViewListener.startListening(*m_rView.GetWindow());
}

css::lang::Locale Document::retrieveLocale()
{
    SolarMutexGuard aGuard;
    return m_rEngine.GetLocale();
}

::sal_Int32 Document::retrieveParagraphIndex(Paragraph const * pParagraph)
{
    ::osl::MutexGuard aInternalGuard(GetMutex());

    // If a client holds on to a Paragraph that is no longer visible, it can
    // happen that this Paragraph lies outside the range from m_aVisibleBegin
    // to m_aVisibleEnd.  In that case, return -1 instead of a valid index:
    auto nPara(pParagraph->getNumber());
    return nPara < m_nVisibleBegin || nPara >= m_nVisibleEnd
        ? -1 : static_cast< ::sal_Int32 >(nPara - m_nVisibleBegin);
        // XXX  numeric overflow
}

::sal_Int64 Document::retrieveParagraphState(Paragraph const * pParagraph)
{
    ::osl::MutexGuard aInternalGuard(GetMutex());

    // If a client holds on to a Paragraph that is no longer visible, it can
    // happen that this Paragraph lies outside the range from m_aVisibleBegin
    // to m_aVisibleEnd.  In that case, it is neither VISIBLE nor SHOWING:
    ::sal_Int64 nState
          = css::accessibility::AccessibleStateType::ENABLED
          | css::accessibility::AccessibleStateType::SENSITIVE
          | css::accessibility::AccessibleStateType::FOCUSABLE
          | css::accessibility::AccessibleStateType::MULTI_LINE;
    if (!m_rView.IsReadOnly())
        nState |= css::accessibility::AccessibleStateType::EDITABLE;
    auto nPara(pParagraph->getNumber());
    if (nPara >= m_nVisibleBegin && nPara < m_nVisibleEnd)
    {
        nState
            |= css::accessibility::AccessibleStateType::VISIBLE
            | css::accessibility::AccessibleStateType::SHOWING;
        if (nPara == m_nFocused)
            nState |= css::accessibility::AccessibleStateType::FOCUSED;
    }
    return nState;
};

css::awt::Rectangle
Document::retrieveParagraphBounds(Paragraph const * pParagraph,
                                  bool bAbsolute)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());

    // If a client holds on to a Paragraph that is no longer visible (as it
    // scrolled out the top of the view), it can happen that this Paragraph
    // lies before m_aVisibleBegin.  In that case, calculate the vertical
    // position of the Paragraph starting at paragraph 0, otherwise optimize
    // and start at m_aVisibleBegin:
    auto nPara(pParagraph->getNumber());
    auto lAddHeight = [](const sal_Int32& rSum, const ParagraphInfo& rParagraph) {
        return rSum + rParagraph.getHeight(); };
    ::sal_Int32 nPos;
    if (nPara < m_nVisibleBegin)
        nPos = std::accumulate(m_aParagraphs.begin(), getIter(nPara), sal_Int32(0), lAddHeight);
    else
        nPos = std::accumulate(visibleBegin(), getIter(nPara), m_nViewOffset - m_nVisibleBeginOffset, lAddHeight);

    Point aOrig(0, 0);
    if (bAbsolute)
        aOrig = Point(m_rView.GetWindow()->OutputToAbsoluteScreenPixel(aOrig));

    return css::awt::Rectangle(
        static_cast< ::sal_Int32 >(aOrig.X()),
        static_cast< ::sal_Int32 >(aOrig.Y()) + nPos - m_nViewOffset,
        m_rView.GetWindow()->GetOutputSizePixel().Width(), getIter(nPara)->getHeight());
        // XXX  numeric overflow (3x)
}

OUString
Document::retrieveParagraphText(Paragraph const * pParagraph)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    return m_rEngine.GetText(static_cast< ::sal_uInt32 >(pParagraph->getNumber()));
        // numeric overflow cannot happen here
}

void Document::retrieveParagraphSelection(Paragraph const * pParagraph,
                                          ::sal_Int32 * pBegin,
                                          ::sal_Int32 * pEnd)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::TextSelection const & rSelection = m_rView.GetSelection();
    Paragraphs::size_type nNumber = pParagraph->getNumber();
    TextPaM aStartPaM( rSelection.GetStart() );
    TextPaM aEndPaM( rSelection.GetEnd() );
    TextPaM aMinPaM( std::min( aStartPaM, aEndPaM ) );
    TextPaM aMaxPaM( std::max( aStartPaM, aEndPaM ) );

    if ( nNumber >= aMinPaM.GetPara() && nNumber <= aMaxPaM.GetPara() )
    {
        *pBegin = nNumber > aMinPaM.GetPara() ? 0 : aMinPaM.GetIndex();
            // XXX numeric overflow
        *pEnd = nNumber < aMaxPaM.GetPara()
            ? m_rEngine.GetText(static_cast< ::sal_uInt32 >(nNumber)).getLength()
            : aMaxPaM.GetIndex();
            // XXX  numeric overflow (3x)

        if ( aStartPaM > aEndPaM )
            std::swap( *pBegin, *pEnd );
    }
    else
    {
        *pBegin = 0;
        *pEnd = 0;
    }
}

::sal_Int32 Document::retrieveParagraphCaretPosition(Paragraph const * pParagraph)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::TextSelection const & rSelection = m_rView.GetSelection();
    Paragraphs::size_type nNumber = pParagraph->getNumber();
    TextPaM aEndPaM( rSelection.GetEnd() );

    return aEndPaM.GetPara() == nNumber ? aEndPaM.GetIndex() : -1;
}

css::awt::Rectangle
Document::retrieveCharacterBounds(Paragraph const * pParagraph,
                                  ::sal_Int32 nIndex)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
    sal_Int32 nLength = m_rEngine.GetText(nNumber).getLength();
        // XXX  numeric overflow
    if (nIndex < 0 || nIndex > nLength)
        throw css::lang::IndexOutOfBoundsException(
            u"textwindowaccessibility.cxx:"
            " Document::retrieveCharacterAttributes"_ustr,
            getXWeak());
    css::awt::Rectangle aBounds( 0, 0, 0, 0 );
    if ( nIndex == nLength )
    {
        aBounds
            = vcl::unohelper::ConvertToAWTRect(m_rEngine.PaMtoEditCursor(::TextPaM(nNumber, nIndex)));
    }
    else
    {
        ::tools::Rectangle aLeft(
            m_rEngine.PaMtoEditCursor(::TextPaM(nNumber, nIndex)));
            // XXX  numeric overflow
        ::tools::Rectangle aRight(
            m_rEngine.PaMtoEditCursor(::TextPaM(nNumber, nIndex + 1)));
            // XXX  numeric overflow (2x)
        // FIXME  If the vertical extends of the two cursors do not match, assume
        // nIndex is the last character on the line; the bounding box will then
        // extend to m_rEngine.GetMaxTextWidth():
        ::sal_Int32 nWidth = (aLeft.Top() == aRight.Top()
                            && aLeft.Bottom() == aRight.Bottom())
            ? static_cast< ::sal_Int32 >(aRight.Left() - aLeft.Left())
            : static_cast< ::sal_Int32 >(m_rEngine.GetMaxTextWidth()
                                        - aLeft.Left());
            // XXX  numeric overflow (4x)
        aBounds = css::awt::Rectangle(static_cast< ::sal_Int32 >(aLeft.Left()),
                                        static_cast< ::sal_Int32 >(aLeft.Top() - m_nViewOffset),
                                        nWidth,
                                        static_cast< ::sal_Int32 >(aLeft.Bottom()
                                                                    - aLeft.Top()));
            // XXX  numeric overflow (4x)
    }
    return aBounds;
}

::sal_Int32 Document::retrieveCharacterIndex(Paragraph const * pParagraph,
                                             css::awt::Point const & rPoint)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
        // XXX  numeric overflow
    ::TextPaM aPaM(m_rEngine.GetPaM(::Point(static_cast< tools::Long >(rPoint.X),
                                            static_cast< tools::Long >(rPoint.Y))));
        // XXX  numeric overflow (2x)
    return aPaM.GetPara() == nNumber ? aPaM.GetIndex() : -1;
        // XXX  numeric overflow
}

css::uno::Sequence< css::beans::PropertyValue >
Document::retrieveCharacterAttributes(
    Paragraph const * pParagraph, ::sal_Int32 nIndex,
    const css::uno::Sequence< OUString >& aRequestedAttributes)
{
    SolarMutexGuard aGuard;

    vcl::Font aFont = m_rEngine.GetFont();
    const sal_Int32 AttributeCount = 9;
    std::vector< css::beans::PropertyValue > aAttribs;
    aAttribs.reserve(AttributeCount);

    css::beans::PropertyValue aAttrib;
    aAttrib.Handle = -1;
    aAttrib.State = css::beans::PropertyState_DIRECT_VALUE;

    //character background color
    aAttrib.Name = "CharBackColor";
    aAttrib.Value = mapFontColor( aFont.GetFillColor() );
    aAttribs.push_back(aAttrib);

    //character color
    aAttrib.Name = "CharColor";
    //aAttrib.Value = mapFontColor( aFont.GetColor() );
    aAttrib.Value = mapFontColor( m_rEngine.GetTextColor() );
    aAttribs.push_back(aAttrib);

    //character font name
    aAttrib.Name = "CharFontName";
    aAttrib.Value <<= aFont.GetFamilyName();
    aAttribs.push_back(aAttrib);

    //character height
    aAttrib.Name = "CharHeight";
    aAttrib.Value <<= static_cast<sal_Int16>(aFont.GetFontHeight());
    aAttribs.push_back(aAttrib);

    //character posture
    aAttrib.Name = "CharPosture";
    aAttrib.Value <<= static_cast<sal_Int16>(aFont.GetItalic());
    aAttribs.push_back(aAttrib);

    //character relief
    /*
    aAttrib.Name = "CharRelief";
    aAttrib.Value = css::uno::Any( (sal_Int16)aFont.GetRelief() );
    aAttribs.push_back(aAttrib);
    */

    //character strikeout
    aAttrib.Name = "CharStrikeout";
    aAttrib.Value <<= static_cast<sal_Int16>(aFont.GetStrikeout());
    aAttribs.push_back(aAttrib);

    //character underline
    aAttrib.Name = "CharUnderline";
    aAttrib.Value <<= static_cast<sal_Int16>(aFont.GetUnderline());
    aAttribs.push_back(aAttrib);

    //character weight
    aAttrib.Name = "CharWeight";
    aAttrib.Value <<= static_cast<float>(aFont.GetWeight());
    aAttribs.push_back(aAttrib);

    //character alignment
    aAttrib.Name = "ParaAdjust";
    aAttrib.Value <<= static_cast<sal_Int16>(m_rEngine.GetTextAlign());
    aAttribs.push_back(aAttrib);

    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
        // XXX  numeric overflow
        // nIndex can be equal to getLength();
    if (nIndex < 0 || nIndex > m_rEngine.GetText(nNumber).getLength())
        throw css::lang::IndexOutOfBoundsException(
            u"textwindowaccessibility.cxx:"
            " Document::retrieveCharacterAttributes"_ustr,
            getXWeak());


    // retrieve run attributes
    tPropValMap aCharAttrSeq;
    retrieveRunAttributesImpl( pParagraph, nIndex, aRequestedAttributes, aCharAttrSeq );

    for (const css::beans::PropertyValue& rAttrib : aAttribs)
    {
        aCharAttrSeq[ rAttrib.Name ] = rAttrib;
    }

    const css::uno::Sequence< css::beans::PropertyValue > aRes = comphelper::mapValuesToSequence( aCharAttrSeq );

    // sort the attributes
    auto nLength = static_cast<size_t>(aRes.getLength());
    std::vector<sal_Int32>  pIndices(nLength);
    std::iota(pIndices.begin(), pIndices.end(), 0);
    std::sort(pIndices.begin(), pIndices.end(),
        [&aRes](sal_Int32 a, sal_Int32 b) { return aRes[a].Name < aRes[b].Name; });

    // create sorted sequences according to index array
    std::vector<css::beans::PropertyValue> aNewValues;
    aNewValues.reserve(nLength);
    std::transform(pIndices.begin(), pIndices.end(), std::back_inserter(aNewValues),
        [&aRes](const sal_Int32 nIdx) -> const css::beans::PropertyValue& { return aRes[nIdx]; });

    return comphelper::containerToSequence(aNewValues);
}

void Document::retrieveRunAttributesImpl(
    Paragraph const * pParagraph, ::sal_Int32 Index,
    const css::uno::Sequence< OUString >& RequestedAttributes,
    tPropValMap& rRunAttrSeq)
{
    ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >( pParagraph->getNumber() );
    ::TextPaM aPaM( nNumber, Index );
        // XXX  numeric overflow
    ::TextAttribFontColor const * pColor
          = static_cast< ::TextAttribFontColor const * >(
              m_rEngine.FindAttrib( aPaM, TEXTATTR_FONTCOLOR ) );
    ::TextAttribFontWeight const * pWeight
          = static_cast< ::TextAttribFontWeight const * >(
              m_rEngine.FindAttrib( aPaM, TEXTATTR_FONTWEIGHT ) );
    tPropValMap aRunAttrSeq;
    if ( pColor )
    {
        css::beans::PropertyValue aPropVal{
            "CharColor", -1,
            mapFontColor(pColor->GetColor()),
            css::beans::PropertyState_DIRECT_VALUE};
        aRunAttrSeq[ aPropVal.Name ] = aPropVal;
    }
    if ( pWeight )
    {
        css::beans::PropertyValue aPropVal{
            "CharWeight", -1,
            mapFontWeight(pWeight->getFontWeight()),
            css::beans::PropertyState_DIRECT_VALUE};
        aRunAttrSeq[ aPropVal.Name ] = aPropVal;
    }
    if ( !RequestedAttributes.hasElements() )
    {
        rRunAttrSeq = std::move(aRunAttrSeq);
    }
    else
    {
        for ( const OUString& rReqAttr : RequestedAttributes )
        {
            tPropValMap::iterator aIter = aRunAttrSeq.find( rReqAttr );
            if ( aIter != aRunAttrSeq.end() )
            {
                rRunAttrSeq[ (*aIter).first ] = (*aIter).second;
            }
        }
    }
}

css::uno::Sequence< css::beans::PropertyValue >
Document::retrieveRunAttributes(
    Paragraph const * pParagraph, ::sal_Int32 Index,
    const css::uno::Sequence< OUString >& RequestedAttributes)
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard( GetMutex() );
    ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >( pParagraph->getNumber() );
        // XXX  numeric overflow
    if ( Index < 0 || Index >= m_rEngine.GetText(nNumber).getLength() )
        throw css::lang::IndexOutOfBoundsException(
            u"textwindowaccessibility.cxx:"
            " Document::retrieveRunAttributes"_ustr,
            getXWeak() );

    tPropValMap aRunAttrSeq;
    retrieveRunAttributesImpl( pParagraph, Index, RequestedAttributes, aRunAttrSeq );
    return comphelper::mapValuesToSequence( aRunAttrSeq );
}

void Document::changeParagraphText(Paragraph const * pParagraph,
                                   OUString const & rText)
{
    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard(GetMutex());
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
            // XXX  numeric overflow
        changeParagraphText(nNumber, 0, m_rEngine.GetTextLen(nNumber), false,
                            false, rText);
    }
}

void Document::changeParagraphText(Paragraph const * pParagraph,
                                   ::sal_Int32 nBegin, ::sal_Int32 nEnd,
                                   bool bCut, bool bPaste,
                                   OUString const & rText)
{
    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard(GetMutex());
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
            // XXX  numeric overflow
        if (nBegin < 0 || nBegin > nEnd
            || nEnd > m_rEngine.GetText(nNumber).getLength())
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::changeParagraphText"_ustr,
                getXWeak());
        changeParagraphText(nNumber, static_cast< ::sal_uInt16 >(nBegin),
                            static_cast< ::sal_uInt16 >(nEnd), bCut, bPaste, rText);
            // XXX  numeric overflow (2x)
    }
}

void Document::copyParagraphText(Paragraph const * pParagraph,
                                 ::sal_Int32 nBegin, ::sal_Int32 nEnd)
{
    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard(GetMutex());
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
            // XXX  numeric overflow
        if (nBegin < 0 || nBegin > nEnd
            || nEnd > m_rEngine.GetText(nNumber).getLength())
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::copyParagraphText"_ustr,
                getXWeak());
        m_rView.SetSelection(
            ::TextSelection(::TextPaM(nNumber, nBegin),
                            ::TextPaM(nNumber, nEnd)));
            // XXX  numeric overflow (2x)
        m_rView.Copy();
    }
}

void Document::changeParagraphAttributes(
    Paragraph const * pParagraph, ::sal_Int32 nBegin, ::sal_Int32 nEnd,
    css::uno::Sequence< css::beans::PropertyValue > const & rAttributeSet)
{
    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard(GetMutex());
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
        // XXX  numeric overflow
        if (nBegin < 0 || nBegin > nEnd
            || nEnd > m_rEngine.GetText(nNumber).getLength())
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::changeParagraphAttributes"_ustr,
                getXWeak());

        // FIXME  The new attributes are added to any attributes already set,
        // they do not replace the old attributes as required by
        // XAccessibleEditableText.setAttributes:
        for (const auto& rAttr : rAttributeSet)
            if ( rAttr.Name == "CharColor" )
                m_rEngine.SetAttrib(::TextAttribFontColor(
                                        mapFontColor(rAttr.Value)),
                                        nNumber, nBegin, nEnd);
                    // XXX  numeric overflow (2x)
            else if ( rAttr.Name == "CharWeight" )
                m_rEngine.SetAttrib(::TextAttribFontWeight(
                                        mapFontWeight(rAttr.Value)),
                                        nNumber, nBegin, nEnd);
                    // XXX  numeric overflow (2x)
    }
}

void Document::changeParagraphSelection(Paragraph const * pParagraph,
                                        ::sal_Int32 nBegin, ::sal_Int32 nEnd)
{
    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard(GetMutex());
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >(pParagraph->getNumber());
            // XXX  numeric overflow
        if (nBegin < 0 || nBegin > nEnd
            || nEnd > m_rEngine.GetText(nNumber).getLength())
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::changeParagraphSelection"_ustr,
                getXWeak());
        m_rView.SetSelection(
            ::TextSelection(::TextPaM(nNumber, nBegin),
                            ::TextPaM(nNumber, nEnd)));
            // XXX  numeric overflow (2x)
    }
}

css::i18n::Boundary
Document::retrieveParagraphLineBoundary( Paragraph const * pParagraph,
                                         ::sal_Int32 nIndex, ::sal_Int32 *pLineNo )
{
    css::i18n::Boundary aBoundary;
    aBoundary.startPos = nIndex;
    aBoundary.endPos = nIndex;

    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard( GetMutex() );
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >( pParagraph->getNumber() );
        if ( nIndex < 0 || nIndex > m_rEngine.GetText( nNumber ).getLength() )
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::retrieveParagraphLineBoundary"_ustr,
                getXWeak() );
        ::sal_Int32 nLineStart = 0;
        ::sal_Int32 nLineEnd = 0;
        ::sal_uInt16 nLineCount = m_rEngine.GetLineCount( nNumber );
        for ( ::sal_uInt16 nLine = 0; nLine < nLineCount; ++nLine )
        {
            nLineStart = nLineEnd;
            nLineEnd += m_rEngine.GetLineLen( nNumber, nLine );
            if ( nIndex >= nLineStart && ( ( nLine == nLineCount - 1 ) ? nIndex <= nLineEnd : nIndex < nLineEnd ) )
            {
                aBoundary.startPos = nLineStart;
                aBoundary.endPos = nLineEnd;
                if( pLineNo )
                    pLineNo[0] = nLine;
                break;
            }
        }
    }

    return aBoundary;
}

css::i18n::Boundary
Document::retrieveParagraphBoundaryOfLine( Paragraph const * pParagraph,
                                           ::sal_Int32 nLineNo )
{
    css::i18n::Boundary aBoundary;
    aBoundary.startPos = 0;
    aBoundary.endPos = 0;

    SolarMutexGuard aGuard;
    {
        ::osl::MutexGuard aInternalGuard( GetMutex() );
        ::sal_uInt32 nNumber = static_cast< ::sal_uInt32 >( pParagraph->getNumber() );
        if ( nLineNo >= m_rEngine.GetLineCount( nNumber ) )
            throw css::lang::IndexOutOfBoundsException(
                u"textwindowaccessibility.cxx:"
                " Document::retrieveParagraphBoundaryOfLine"_ustr,
                getXWeak() );
        ::sal_Int32 nLineStart = 0;
        ::sal_Int32 nLineEnd = 0;
        for ( ::sal_Int32 nLine = 0; nLine <= nLineNo; ++nLine )
        {
            nLineStart = nLineEnd;
            nLineEnd += m_rEngine.GetLineLen( nNumber, nLine );
        }

        aBoundary.startPos = nLineStart;
        aBoundary.endPos = nLineEnd;
    }

    return aBoundary;
}

sal_Int32 Document::retrieveParagraphLineWithCursor( Paragraph const * pParagraph )
{
    SolarMutexGuard aGuard;
    ::osl::MutexGuard aInternalGuard(GetMutex());
    ::TextSelection const & rSelection = m_rView.GetSelection();
    Paragraphs::size_type nNumber = pParagraph->getNumber();
    TextPaM aEndPaM( rSelection.GetEnd() );

    return aEndPaM.GetPara() == nNumber
        ? m_rView.GetLineNumberOfCursorInSelection() : -1;
}


css::uno::Reference< css::accessibility::XAccessibleRelationSet >
Document::retrieveParagraphRelationSet( Paragraph const * pParagraph )
{
    ::osl::MutexGuard aInternalGuard( GetMutex() );

    rtl::Reference<::utl::AccessibleRelationSetHelper> pRelationSetHelper = new ::utl::AccessibleRelationSetHelper();

    auto nPara(pParagraph->getNumber());

    if (nPara > m_nVisibleBegin && nPara < m_nVisibleEnd)
    {
        css::uno::Sequence<css::uno::Reference<css::accessibility::XAccessible>> aSequence { getAccessibleChild(getIter(nPara - 1)) };
        css::accessibility::AccessibleRelation aRelation(css::accessibility::AccessibleRelationType_CONTENT_FLOWS_FROM, aSequence);
        pRelationSetHelper->AddRelation( aRelation );
    }

    if (nPara >= m_nVisibleBegin && m_nVisibleEnd > 1 && nPara < m_nVisibleEnd - 1)
    {
        css::uno::Sequence<css::uno::Reference<css::accessibility::XAccessible>> aSequence { getAccessibleChild(getIter(nPara + 1)) };
        css::accessibility::AccessibleRelation aRelation( css::accessibility::AccessibleRelationType_CONTENT_FLOWS_TO, aSequence );
        pRelationSetHelper->AddRelation( aRelation );
    }

    return pRelationSetHelper;
}

// virtual
sal_Int64 SAL_CALL Document::getAccessibleChildCount()
{
    ::comphelper::OExternalLockGuard aGuard(this);
    return m_nVisibleEnd - m_nVisibleBegin;
}

// virtual
css::uno::Reference< css::accessibility::XAccessible > SAL_CALL
Document::getAccessibleChild(sal_Int64 i)
{
    ::comphelper::OExternalLockGuard aGuard(this);
    if (i < 0 || o3tl::make_unsigned(i) >= m_nVisibleEnd - m_nVisibleBegin)
        throw css::lang::IndexOutOfBoundsException(
            u"textwindowaccessibility.cxx:"
            " Document::getAccessibleChild"_ustr,
            getXWeak());
    return getAccessibleChild(getIter(m_nVisibleBegin + i));
}

// virtual
::sal_Int16 SAL_CALL Document::getAccessibleRole()
{
    return css::accessibility::AccessibleRole::TEXT_FRAME;
}

// virtual
css::uno::Reference< css::accessibility::XAccessible > SAL_CALL
Document::getAccessibleAtPoint(css::awt::Point const & rPoint)
{
    ::comphelper::OExternalLockGuard aGuard(this);
    if (rPoint.X >= 0
        && rPoint.X < m_rView.GetWindow()->GetOutputSizePixel().Width()
        && rPoint.Y >= 0 && rPoint.Y < m_nViewHeight)
    {
        ::sal_Int32 nOffset = m_nViewOffset + rPoint.Y; // XXX  numeric overflow
        ::sal_Int32 nPos = m_nViewOffset - m_nVisibleBeginOffset;
        for (Paragraphs::iterator aIt(visibleBegin()), aEnd(visibleEnd()); aIt != aEnd; ++aIt)
        {
            nPos += aIt->getHeight(); // XXX  numeric overflow
            if (nOffset < nPos)
                return getAccessibleChild(aIt);
        }
    }
    return nullptr;
}
void Document::FillAccessibleStateSet( sal_Int64& rStateSet )
{
    VCLXAccessibleComponent::FillAccessibleStateSet( rStateSet );
    if (!m_rView.IsReadOnly())
        rStateSet |= css::accessibility::AccessibleStateType::EDITABLE;
}

void    Document::FillAccessibleRelationSet( utl::AccessibleRelationSetHelper& rRelationSet )
{
    if( getAccessibleParent()->getAccessibleContext()->getAccessibleRole() == css::accessibility::AccessibleRole::SCROLL_PANE )
    {
        css::uno::Sequence<css::uno::Reference<css::accessibility::XAccessible>> aSequence {  getAccessibleParent() };
        rRelationSet.AddRelation( css::accessibility::AccessibleRelation( css::accessibility::AccessibleRelationType_MEMBER_OF, aSequence ) );
    }
    else
    {
         VCLXAccessibleComponent::FillAccessibleRelationSet(rRelationSet);
    }
}
// virtual
void SAL_CALL Document::disposing()
{
    m_aEngineListener.endListening();
    m_aViewListener.endListening();
    disposeParagraphs();
    VCLXAccessibleComponent::disposing();
}

// virtual
void Document::Notify(::SfxBroadcaster &, ::SfxHint const & rHint)
{
    switch (rHint.GetId())
    {
    case SfxHintId::TextParaInserted:
    case SfxHintId::TextParaRemoved:
        // SfxHintId::TextParaInserted and SfxHintId::TextParaRemoved are sent at
        // "unsafe" times (when the text engine has not yet re-formatted its
        // content), so that for example calling ::TextEngine::GetTextHeight
        // from within the code that handles SfxHintId::TextParaInserted causes
        // trouble within the text engine.  Therefore, these hints are just
        // buffered until a following ::TextEngine::FormatDoc causes a
        // SfxHintId::TextFormatted to come in:
    case SfxHintId::TextFormatPara:
        // ::TextEngine::FormatDoc sends a sequence of
        // SfxHintId::TextFormatParas, followed by an optional
        // SfxHintId::TextHeightChanged, followed in all cases by one
        // SfxHintId::TextFormatted.  Only the SfxHintId::TextFormatParas contain
        // the numbers of the affected paragraphs, but they are sent
        // before the changes are applied.  Therefore, SfxHintId::TextFormatParas
        // are just buffered until another hint comes in:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;

            const TextHint& rTextHint = static_cast<const TextHint&>(rHint);
            m_aParagraphNotifications.push(rTextHint);
            break;
        }
    case SfxHintId::TextFormatted:
    case SfxHintId::TextHeightChanged:
    case SfxHintId::TextModified:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;
            handleParagraphNotifications();
            break;
        }
    case SfxHintId::TextViewScrolled:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;
            handleParagraphNotifications();

            ::sal_Int32 nOffset = static_cast< ::sal_Int32 >(
                m_rView.GetStartDocPos().Y());
                // XXX  numeric overflow
            if (nOffset != m_nViewOffset)
            {
                m_nViewOffset = nOffset;

                Paragraphs::iterator aOldVisibleBegin(visibleBegin());
                Paragraphs::iterator aOldVisibleEnd(visibleEnd());

                determineVisibleRange();

                notifyVisibleRangeChanges(aOldVisibleBegin,
                                            aOldVisibleEnd,
                                            m_aParagraphs.end());
            }
            break;
        }
    case SfxHintId::TextViewSelectionChanged:
    case SfxHintId::TextViewCaretChanged:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;

            if (m_aParagraphNotifications.empty())
            {
                handleSelectionChangeNotification();
            }
            else
            {
                // SfxHintId::TextViewSelectionChanged is sometimes sent at
                // "unsafe" times (when the text engine has not yet re-
                // formatted its content), so that for example calling
                // ::TextEngine::GetTextHeight from within the code that
                // handles a previous SfxHintId::TextParaInserted causes
                // trouble within the text engine.  Therefore, these
                // hints are just buffered (along with
                // SfxHintId::TextParaInserted/REMOVED/FORMATPARA) until a
                // following ::TextEngine::FormatDoc causes a
                // SfxHintId::TextFormatted to come in:
                m_bSelectionChangedNotification = true;
            }
            break;
        }
    default: break;
    }
}

IMPL_LINK(Document, WindowEventHandler, ::VclWindowEvent&, rEvent, void)
{
    switch (rEvent.GetId())
    {
    case VclEventId::WindowResize:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;

            ::sal_Int32 nHeight = static_cast< ::sal_Int32 >(
                m_rView.GetWindow()->GetOutputSizePixel().Height());
                // XXX  numeric overflow
            if (nHeight != m_nViewHeight)
            {
                m_nViewHeight = nHeight;

                Paragraphs::iterator aOldVisibleBegin(visibleBegin());
                Paragraphs::iterator aOldVisibleEnd(visibleEnd());

                determineVisibleRange();

                notifyVisibleRangeChanges(aOldVisibleBegin, aOldVisibleEnd,
                                            m_aParagraphs.end());
            }
            break;
        }
    case VclEventId::WindowGetFocus:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;
            //to enable the PARAGRAPH to get focus for multiline edit
            sal_Int64 count = getAccessibleChildCount();
            bool bEmpty = m_nFocused == m_nVisibleEnd && count == 1;
            if (bEmpty || (m_nFocused >= m_nVisibleBegin && m_nFocused < m_nVisibleEnd))
            {
                Paragraphs::iterator aTemp = bEmpty ? visibleBegin() : focused();
                ::rtl::Reference< Paragraph > xParagraph(getParagraph(aTemp));
                if (xParagraph.is())
                {
                    xParagraph->notifyEvent(
                        css::accessibility::AccessibleEventId::
                        STATE_CHANGED,
                        css::uno::Any(),
                        css::uno::Any(
                            css::accessibility::AccessibleStateType::
                            FOCUSED));
                }
            }
            break;
        }
    case VclEventId::WindowLoseFocus:
        {
            ::osl::MutexGuard aInternalGuard(GetMutex());
            if (!isAlive())
                break;
            //to enable the PARAGRAPH to get focus for multiline edit
            sal_Int64 count = getAccessibleChildCount();
            bool bEmpty = m_nFocused == m_nVisibleEnd && count == 1;
            if (bEmpty || (m_nFocused >= m_nVisibleBegin && m_nFocused < m_nVisibleEnd))
            {
                Paragraphs::iterator aTemp = bEmpty ? visibleBegin() : focused();
                ::rtl::Reference< Paragraph > xParagraph(getParagraph(aTemp));
                if (xParagraph.is())
                    xParagraph->notifyEvent(
                        css::accessibility::AccessibleEventId::
                        STATE_CHANGED,
                        css::uno::Any(
                            css::accessibility::AccessibleStateType::
                            FOCUSED),
                        css::uno::Any());
            }
            break;
        }
    default: break;
    }
}

::rtl::Reference< Paragraph >
Document::getParagraph(Paragraphs::iterator const & rIt)
{
    return rIt->getParagraph().get();
}

css::uno::Reference< css::accessibility::XAccessible >
Document::getAccessibleChild(Paragraphs::iterator const & rIt)
{
    rtl::Reference< Paragraph > xParagraph(rIt->getParagraph());
    if (!xParagraph.is())
    {
        xParagraph = new Paragraph(this, rIt - m_aParagraphs.begin());
        rIt->setParagraph(xParagraph);
    }
    return xParagraph;
}

void Document::determineVisibleRange()
{
    auto const nEnd = m_aParagraphs.size();

    m_nVisibleBegin = nEnd;
    m_nVisibleEnd = nEnd;
    m_nVisibleBeginOffset = 0;

    ::sal_Int32 nPos = 0;
    for (Paragraphs::size_type i = 0; m_nVisibleEnd == nEnd && i != nEnd; ++i)
    {
        ::sal_Int32 const nOldPos = nPos;
        nPos += m_aParagraphs[i].getHeight(); // XXX  numeric overflow
        if (m_nVisibleBegin == nEnd)
        {
            if (nPos >= m_nViewOffset)
            {
                m_nVisibleBegin = i;
                m_nVisibleBeginOffset = m_nViewOffset - nOldPos;
            }
        }
        else
        {
            if (nPos >= m_nViewOffset + m_nViewHeight) // XXX  numeric overflow
            {
                m_nVisibleEnd = i;
            }
        }
    }

    SAL_WARN_IF(
            !((m_nVisibleBegin == m_aParagraphs.size() && m_nVisibleEnd == m_aParagraphs.size() && m_nVisibleBeginOffset == 0)
                || (m_nVisibleBegin < m_nVisibleEnd && m_nVisibleBeginOffset >= 0)),
            "accessibility",
            "invalid visible range");
}

void Document::notifyVisibleRangeChanges(
    Paragraphs::iterator const & rOldVisibleBegin,
    Paragraphs::iterator const & rOldVisibleEnd,
    Paragraphs::iterator const & rInserted)
{
    // XXX  Replace this code that determines which paragraphs have changed from
    // invisible to visible or vice versa with a better algorithm.
    auto aVisibleBegin = visibleBegin(), aVisibleEnd = visibleEnd();
    for (Paragraphs::iterator aIt(rOldVisibleBegin); aIt != rOldVisibleEnd;
         ++aIt)
    {
        if (aIt != rInserted && (aIt < aVisibleBegin || aIt >= aVisibleEnd))
            NotifyAccessibleEvent(
                css::accessibility::AccessibleEventId::
                CHILD,
                css::uno::Any(getAccessibleChild(aIt)),
                css::uno::Any());
    }
    for (Paragraphs::iterator aIt(aVisibleBegin); aIt != aVisibleEnd; ++aIt)
    {
        if (aIt == rInserted
            || aIt < rOldVisibleBegin || aIt >= rOldVisibleEnd)
            NotifyAccessibleEvent(
                css::accessibility::AccessibleEventId::
                CHILD,
                css::uno::Any(),
                css::uno::Any(getAccessibleChild(aIt)));
    }
}

void
Document::changeParagraphText(::sal_uInt32 nNumber, ::sal_uInt16 nBegin, ::sal_uInt16 nEnd,
                              bool bCut, bool bPaste,
                              OUString const & rText)
{
    m_rView.SetSelection(::TextSelection(::TextPaM(nNumber, nBegin),
                                         ::TextPaM(nNumber, nEnd)));
    if (bCut)
        m_rView.Cut();
    else if (nBegin != nEnd)
        m_rView.DeleteSelected();
    if (bPaste)
        m_rView.Paste();
    else if (!rText.isEmpty())
        m_rView.InsertText(rText);
}

void Document::handleParagraphNotifications()
{
    // Recursion is possible, e.g. when SfxHintId::TextParaInserted is being handled,
    // and TextEngine::GetTextHeight is called for the paragraph being inserted; that
    // tries to handle the following SfxHintId::TextFormatPara notification at the
    // moment when the respective element hasn't yet been inserted into m_aParagraphs,
    // which could crash. Therefore, re-entry is not allowed. The handling is done in
    // a loop anyway, so it will process all of them in due order.
    // See also comments in Document::Notify.
    if (m_bInParagraphNotificationsHandler)
        return;
    m_bInParagraphNotificationsHandler = true;
    while (!m_aParagraphNotifications.empty())
    {
        ::TextHint aHint(m_aParagraphNotifications.front());
        m_aParagraphNotifications.pop();
        switch (aHint.GetId())
        {
        case SfxHintId::TextParaInserted:
            {
                ::sal_uInt32 n = static_cast< ::sal_uInt32 >( aHint.GetValue() );
                assert(n <= m_aParagraphs.size() && "bad SfxHintId::TextParaInserted event");

                // Save the values of old offsets, and adjust the old values so that they
                // reflect the insertion of the new paragraph:
                Paragraphs::size_type nOldVisibleBegin = m_nVisibleBegin;
                Paragraphs::size_type nOldVisibleEnd = m_nVisibleEnd;
                Paragraphs::size_type nOldFocused = m_nFocused;
                if (n <= nOldVisibleBegin)
                    ++nOldVisibleBegin; // XXX  numeric overflow
                if (n <= nOldVisibleEnd)
                    ++nOldVisibleEnd; // XXX  numeric overflow
                if (n <= nOldFocused)
                    ++nOldFocused; // XXX  numeric overflow
                if (sal::static_int_cast<sal_Int32>(n) <= m_nSelectionFirstPara)
                    ++m_nSelectionFirstPara; // XXX  numeric overflow
                if (sal::static_int_cast<sal_Int32>(n) <= m_nSelectionLastPara)
                    ++m_nSelectionLastPara; // XXX  numeric overflow

                Paragraphs::iterator aIns(
                    m_aParagraphs.insert(
                        getIter(n),
                        ParagraphInfo(static_cast< ::sal_Int32 >(
                                          m_rEngine.GetTextHeight(n)))));
                    // XXX  numeric overflow (2x)

                determineVisibleRange();
                m_nFocused = nOldFocused;

                for (Paragraphs::iterator aIt(aIns);;)
                {
                    ++aIt;
                    if (aIt == m_aParagraphs.end())
                        break;
                    ::rtl::Reference< Paragraph > xParagraph(
                        getParagraph(aIt));
                    if (xParagraph.is())
                        xParagraph->numberChanged(true);
                }

                notifyVisibleRangeChanges(getIter(nOldVisibleBegin), getIter(nOldVisibleEnd), aIns);
                break;
            }
        case SfxHintId::TextParaRemoved:
            {
                ::sal_uInt32 n = static_cast< ::sal_uInt32 >( aHint.GetValue() );
                if (n == TEXT_PARA_ALL)
                {
                    for (Paragraphs::iterator aIt(visibleBegin()), aEnd(visibleEnd());
                         aIt != aEnd; ++aIt)
                    {
                        NotifyAccessibleEvent(
                            css::accessibility::AccessibleEventId::
                            CHILD,
                            css::uno::Any(getAccessibleChild(aIt)),
                            css::uno::Any());
                    }
                    disposeParagraphs();
                    m_aParagraphs.clear();
                    determineVisibleRange();
                    m_nSelectionFirstPara = -1;
                    m_nSelectionFirstPos = -1;
                    m_nSelectionLastPara = -1;
                    m_nSelectionLastPos = -1;
                    m_nFocused = m_aParagraphs.size();
                }
                else
                {
                    assert(n < m_aParagraphs.size() && "Bad SfxHintId::TextParaRemoved event");

                    Paragraphs::iterator aIt(getIter(n));

                    // Save the values of old offsets, and adjust the old
                    // values so that they reflect the removal of the paragraph:
                    Paragraphs::size_type nOldVisibleBegin = m_nVisibleBegin;
                    Paragraphs::size_type nOldVisibleEnd = m_nVisibleEnd;
                    bool bWasVisible
                        = nOldVisibleBegin <= n && n < nOldVisibleEnd;
                    Paragraphs::size_type nOldFocused = m_nFocused;
                    bool bWasFocused = n == m_nFocused;
                    if (n < nOldVisibleBegin)
                        --nOldVisibleBegin;
                    if (n < nOldVisibleEnd)
                        --nOldVisibleEnd;
                    if (n < nOldFocused)
                        --nOldFocused;
                    if (sal::static_int_cast<sal_Int32>(n) < m_nSelectionFirstPara)
                        --m_nSelectionFirstPara;
                    else if (sal::static_int_cast<sal_Int32>(n) == m_nSelectionFirstPara)
                    {
                        if (m_nSelectionFirstPara == m_nSelectionLastPara)
                        {
                            m_nSelectionFirstPara = -1;
                            m_nSelectionFirstPos = -1;
                            m_nSelectionLastPara = -1;
                            m_nSelectionLastPos = -1;
                        }
                        else
                        {
                            ++m_nSelectionFirstPara;
                            m_nSelectionFirstPos = 0;
                        }
                    }
                    if (sal::static_int_cast<sal_Int32>(n) < m_nSelectionLastPara)
                        --m_nSelectionLastPara;
                    else if (sal::static_int_cast<sal_Int32>(n) == m_nSelectionLastPara)
                    {
                        assert(m_nSelectionFirstPara < m_nSelectionLastPara && "logic error");
                        --m_nSelectionLastPara;
                        m_nSelectionLastPos = 0x7FFFFFFF;
                    }

                    css::uno::Reference< css::accessibility::XAccessible >
                          xStrong;
                    if (bWasVisible)
                        xStrong = getAccessibleChild(aIt);
                    unotools::WeakReference<Paragraph> xWeak(
                              aIt->getParagraph());
                    aIt = m_aParagraphs.erase(aIt);

                    determineVisibleRange();
                    m_nFocused = bWasFocused ? m_aParagraphs.size() : nOldFocused;

                    for (; aIt != m_aParagraphs.end(); ++aIt)
                    {
                        ::rtl::Reference< Paragraph > xParagraph(
                            getParagraph(aIt));
                        if (xParagraph.is())
                            xParagraph->numberChanged(false);
                    }

                    if (bWasVisible)
                        NotifyAccessibleEvent(
                            css::accessibility::AccessibleEventId::
                            CHILD,
                            css::uno::Any(xStrong),
                            css::uno::Any());

                    rtl::Reference< Paragraph > xComponent( xWeak.get() );
                    if (xComponent.is())
                        xComponent->dispose();

                    notifyVisibleRangeChanges(
                        getIter(nOldVisibleBegin),
                        getIter(nOldVisibleEnd),
                        m_aParagraphs.end());
                }
                break;
            }
        case SfxHintId::TextFormatPara:
            {
                ::sal_uInt32 n = static_cast< ::sal_uInt32 >( aHint.GetValue() );
                assert(n < m_aParagraphs.size() && "Bad SfxHintId::TextFormatPara event");

                m_aParagraphs[static_cast< Paragraphs::size_type >(n)].
                    changeHeight(static_cast< ::sal_Int32 >(
                                     m_rEngine.GetTextHeight(n)));
                    // XXX  numeric overflow
                Paragraphs::iterator aOldVisibleBegin(visibleBegin());
                Paragraphs::iterator aOldVisibleEnd(visibleEnd());
                determineVisibleRange();
                notifyVisibleRangeChanges(aOldVisibleBegin, aOldVisibleEnd,
                                          m_aParagraphs.end());

                if (n < m_aParagraphs.size())
                {
                    Paragraphs::iterator aIt(m_aParagraphs.begin() + n);
                    ::rtl::Reference< Paragraph > xParagraph(getParagraph(aIt));
                    if (xParagraph.is())
                        xParagraph->textChanged();
                }
                break;
            }
        default:
            SAL_WARN("accessibility", "bad buffered hint");
            break;
        }
    }
    m_bInParagraphNotificationsHandler = false;
    if (m_bSelectionChangedNotification)
    {
        m_bSelectionChangedNotification = false;
        handleSelectionChangeNotification();
    }
}

namespace
{

enum class SelChangeType
{
    None, // no change, or invalid
    CaretMove, // neither old nor new have selection, and they are different
    NoSelToSel, // old has no selection but new has selection
    SelToNoSel, // old has selection but new has no selection
    // both old and new have selections
    NoParaChange, // only end index changed inside end para
    EndParaNoMoreBehind, // end para was behind start, but now is same or ahead
    AddedFollowingPara, // selection extended to following paragraph(s)
    ExcludedPreviousPara, // selection shrunk excluding previous paragraph(s)
    ExcludedFollowingPara, // selection shrunk excluding following paragraph(s)
    AddedPreviousPara, // selection extended to previous paragraph(s)
    EndParaBecameBehind // end para was ahead of start, but now is behind
};

SelChangeType getSelChangeType(const TextPaM& Os, const TextPaM& Oe,
                               const TextPaM& Ns, const TextPaM& Ne)
{
    if (Os == Oe) // no old selection
    {
        if (Ns == Ne) // no new selection: only caret moves
            return Os != Ns ? SelChangeType::CaretMove : SelChangeType::None;
        else // old has no selection but new has selection
            return SelChangeType::NoSelToSel;
    }
    else if (Ns == Ne) // old has selection; no new selection
    {
        return SelChangeType::SelToNoSel;
    }
    else if (Os == Ns) // both old and new have selections, and their starts are same
    {
        const sal_Int32 Osp = Os.GetPara(), Oep = Oe.GetPara();
        const sal_Int32 Nsp = Ns.GetPara(), Nep = Ne.GetPara();
        if (Oep == Nep) // end of selection stays in the same paragraph
        {
            //Send text_selection_change event on Nep
            return Oe.GetIndex() != Ne.GetIndex() ? SelChangeType::NoParaChange
                                                  : SelChangeType::None;
        }
        else if (Oep < Nep) // end of selection moved to a following paragraph
        {
            //all the following examples like 1,2->1,3 means that old start select para is 1, old end select para is 2,
            // then press shift up, the new start select para is 1, new end select para is 3;
            //for example, 1, 2 -> 1, 3; 4,1 -> 4, 7; 4,1 -> 4, 2; 4,4->4,5
            if (Nep >= Nsp) // new end para not behind start
            {
                // 1, 2 -> 1, 3; 4, 1 -> 4, 7; 4,4->4,5;
                if (Oep < Osp) // old end was behind start
                {
                    // 4,1 -> 4,7; 4,1 -> 4,4
                    return SelChangeType::EndParaNoMoreBehind;
                }
                else // old end para wasn't behind start
                {
                    // 1, 2 -> 1, 3; 4,4->4,5;
                    return SelChangeType::AddedFollowingPara;
                }
            }
            else // new end para is still behind start
            {
                // 4,1 -> 4,2,
                return SelChangeType::ExcludedPreviousPara;
            }
        }
        else // Oep > Nep => end of selection moved to a previous paragraph
        {
            // 3,2 -> 3,1; 4,7 -> 4,1; 4, 7 -> 4,6; 4,4 -> 4,3
            if (Nep >= Nsp) // new end para is still not behind of start
            {
                // 4,7 ->4,6
                return SelChangeType::ExcludedFollowingPara;
            }
            else // new end para is behind start
            {
                // 3,2 -> 3,1, 4,7 -> 4,1; 4,4->4,3
                if (Oep <= Osp) // it was not ahead already
                {
                    // 3,2 -> 3,1; 4,4->4,3
                    return SelChangeType::AddedPreviousPara;
                }
                else // it was ahead previously
                {
                    // 4,7 -> 4,1
                    return SelChangeType::EndParaBecameBehind;
                }
            }
        }
    }
    return SelChangeType::None;
}

} // namespace

void Document::sendEvent(::sal_Int32 start, ::sal_Int32 end, ::sal_Int16 nEventId)
{
    size_t nAvailDistance = std::distance(m_aParagraphs.begin(), visibleEnd());

    Paragraphs::iterator aEnd(m_aParagraphs.begin());
    size_t nEndDistance = std::min<size_t>(end + 1, nAvailDistance);
    std::advance(aEnd, nEndDistance);

    Paragraphs::iterator aIt(m_aParagraphs.begin());
    size_t nStartDistance = std::min<size_t>(start, nAvailDistance);
    std::advance(aIt, nStartDistance);

    while (aIt < aEnd)
    {
        ::rtl::Reference< Paragraph > xParagraph(getParagraph(aIt));
        if (xParagraph.is())
            xParagraph->notifyEvent(
            nEventId,
                css::uno::Any(), css::uno::Any());
        ++aIt;
    }
}

void Document::handleSelectionChangeNotification()
{
    ::TextSelection const & rSelection = m_rView.GetSelection();
    assert(rSelection.GetStart().GetPara() < m_aParagraphs.size() &&
           rSelection.GetEnd().GetPara() < m_aParagraphs.size() &&
           "bad SfxHintId::TextViewSelectionChanged event");
    ::sal_Int32 nNewFirstPara
          = static_cast< ::sal_Int32 >(rSelection.GetStart().GetPara());
    ::sal_Int32 nNewFirstPos = rSelection.GetStart().GetIndex();
        // XXX  numeric overflow
    ::sal_Int32 nNewLastPara
          = static_cast< ::sal_Int32 >(rSelection.GetEnd().GetPara());
    ::sal_Int32 nNewLastPos = rSelection.GetEnd().GetIndex();
        // XXX  numeric overflow

    // Lose focus:
    Paragraphs::iterator aIt(getIter(nNewLastPara));
    if (m_nFocused < m_aParagraphs.size() && focused() != aIt
        && m_nFocused >= m_nVisibleBegin && m_nFocused < m_nVisibleEnd)
    {
        ::rtl::Reference< Paragraph > xParagraph(getParagraph(focused()));
        if (xParagraph.is())
            xParagraph->notifyEvent(
                css::accessibility::AccessibleEventId::
                STATE_CHANGED,
                css::uno::Any(
                    css::accessibility::AccessibleStateType::FOCUSED),
                css::uno::Any());
    }

    // Gain focus and update cursor position:
    if (aIt >= visibleBegin() && aIt < visibleEnd()
        && (aIt != focused()
            || nNewLastPara != m_nSelectionLastPara
            || nNewLastPos != m_nSelectionLastPos))
    {
        ::rtl::Reference< Paragraph > xParagraph(getParagraph(aIt));
        if (xParagraph.is())
        {
            //disable the first event when user types in empty field.
            sal_Int64 count = getAccessibleChildCount();
            bool bEmpty = count > 1;
            if (aIt != focused() && bEmpty)
                xParagraph->notifyEvent(
                    css::accessibility::AccessibleEventId::
                    STATE_CHANGED,
                    css::uno::Any(),
                    css::uno::Any(
                        css::accessibility::AccessibleStateType::FOCUSED));
            if (nNewLastPara != m_nSelectionLastPara
                || nNewLastPos != m_nSelectionLastPos)
                xParagraph->notifyEvent(
                    css::accessibility::AccessibleEventId::
                    CARET_CHANGED,
                    css::uno::Any( ::sal_Int32 (
                        nNewLastPara == m_nSelectionLastPara
                        ? m_nSelectionLastPos : 0)),
                    css::uno::Any(nNewLastPos));
        }
    }
    m_nFocused = std::distance(m_aParagraphs.begin(), aIt);

    if (m_nSelectionFirstPara != -1)
    {
        sal_Int32 nMin;
        sal_Int32 nMax;
        SelChangeType ret = getSelChangeType(TextPaM(m_nSelectionFirstPara, m_nSelectionFirstPos),
                                             TextPaM(m_nSelectionLastPara, m_nSelectionLastPos),
                                             rSelection.GetStart(), rSelection.GetEnd());
        switch (ret)
        {
            case SelChangeType::None:
                //no event
                break;
            case SelChangeType::CaretMove:
                //only caret moved, already handled in above
                break;
            case SelChangeType::NoSelToSel:
                //old has no selection but new has selection
                nMin = std::min(nNewFirstPara, nNewLastPara);
                nMax = std::max(nNewFirstPara, nNewLastPara);
                sendEvent(nMin, nMax, css::accessibility::AccessibleEventId::SELECTION_CHANGED);
                sendEvent(nMin, nMax,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::SelToNoSel:
                //old has selection but new has no selection.
                nMin = std::min(m_nSelectionFirstPara, m_nSelectionLastPara);
                nMax = std::max(m_nSelectionFirstPara, m_nSelectionLastPara);
                sendEvent(nMin, nMax, css::accessibility::AccessibleEventId::SELECTION_CHANGED);
                sendEvent(nMin, nMax,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::NoParaChange:
                //Send text_selection_change event on Nep
                sendEvent(nNewLastPara, nNewLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::EndParaNoMoreBehind:
                // 4, 1 -> 4, 7; 4,1 -> 4,4
                sendEvent(m_nSelectionLastPara, m_nSelectionFirstPara - 1,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);
                sendEvent(nNewFirstPara + 1, nNewLastPara,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(m_nSelectionLastPara, nNewLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::AddedFollowingPara:
                // 1, 2 -> 1, 4; 4,4->4,5;
                sendEvent(m_nSelectionLastPara + 1, nNewLastPara,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(m_nSelectionLastPara, nNewLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::ExcludedPreviousPara:
                // 4,1 -> 4,3,
                sendEvent(m_nSelectionLastPara + 1, nNewLastPara,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(m_nSelectionLastPara, nNewLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::ExcludedFollowingPara:
                // 4,7 ->4,5;
                sendEvent(nNewLastPara + 1, m_nSelectionLastPara,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(nNewLastPara, m_nSelectionLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::AddedPreviousPara:
                // 3,2 -> 3,1; 4,4->4,3
                sendEvent(nNewLastPara, m_nSelectionLastPara - 1,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(nNewLastPara, m_nSelectionLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
            case SelChangeType::EndParaBecameBehind:
                // 4,7 -> 4,1
                sendEvent(m_nSelectionFirstPara + 1, m_nSelectionLastPara,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);
                sendEvent(nNewLastPara, nNewFirstPara - 1,
                          css::accessibility::AccessibleEventId::SELECTION_CHANGED);

                sendEvent(nNewLastPara, m_nSelectionLastPara,
                          css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED);
                break;
        }
    }

    m_nSelectionFirstPara = nNewFirstPara;
    m_nSelectionFirstPos = nNewFirstPos;
    m_nSelectionLastPara = nNewLastPara;
    m_nSelectionLastPos = nNewLastPos;
}

void Document::disposeParagraphs()
{
    for (auto const& paragraph : m_aParagraphs)
    {
        rtl::Reference< Paragraph > xComponent(
            paragraph.getParagraph().get() );
        if (xComponent.is())
            xComponent->dispose();
    }
}

// static
css::uno::Any Document::mapFontColor(::Color const & rColor)
{
    return css::uno::Any(rColor.GetRGBColor());
        // FIXME  keep transparency?
}

// static
::Color Document::mapFontColor(css::uno::Any const & rColor)
{
    ::Color nColor;
    rColor >>= nColor;
    return nColor;
}

// static
css::uno::Any Document::mapFontWeight(::FontWeight nWeight)
{
    // Map from ::FontWeight to css::awt::FontWeight, depends on order of
    // elements in ::FontWeight (vcl/vclenum.hxx):
    static float const aWeight[]
        = { css::awt::FontWeight::DONTKNOW, // WEIGHT_DONTKNOW
            css::awt::FontWeight::THIN, // WEIGHT_THIN
            css::awt::FontWeight::ULTRALIGHT, // WEIGHT_ULTRALIGHT
            css::awt::FontWeight::LIGHT, // WEIGHT_LIGHT
            css::awt::FontWeight::SEMILIGHT, // WEIGHT_SEMILIGHT
            css::awt::FontWeight::NORMAL, // WEIGHT_NORMAL
            css::awt::FontWeight::NORMAL, // WEIGHT_MEDIUM
            css::awt::FontWeight::SEMIBOLD, // WEIGHT_SEMIBOLD
            css::awt::FontWeight::BOLD, // WEIGHT_BOLD
            css::awt::FontWeight::ULTRABOLD, // WEIGHT_ULTRABOLD
            css::awt::FontWeight::BLACK }; // WEIGHT_BLACK
    return css::uno::Any(aWeight[nWeight]);
}

// static
::FontWeight Document::mapFontWeight(css::uno::Any const & rWeight)
{
    float nWeight = css::awt::FontWeight::NORMAL;
    rWeight >>= nWeight;
    return nWeight <= css::awt::FontWeight::DONTKNOW ? WEIGHT_DONTKNOW
        : nWeight <= css::awt::FontWeight::THIN ? WEIGHT_THIN
        : nWeight <= css::awt::FontWeight::ULTRALIGHT ? WEIGHT_ULTRALIGHT
        : nWeight <= css::awt::FontWeight::LIGHT ? WEIGHT_LIGHT
        : nWeight <= css::awt::FontWeight::SEMILIGHT ? WEIGHT_SEMILIGHT
        : nWeight <= css::awt::FontWeight::NORMAL ? WEIGHT_NORMAL
        : nWeight <= css::awt::FontWeight::SEMIBOLD ? WEIGHT_SEMIBOLD
        : nWeight <= css::awt::FontWeight::BOLD ? WEIGHT_BOLD
        : nWeight <= css::awt::FontWeight::ULTRABOLD ? WEIGHT_ULTRABOLD
        : WEIGHT_BLACK;
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
