diff options
Diffstat (limited to 'accessibility/source/extended/textwindowaccessibility.cxx')
-rw-r--r-- | accessibility/source/extended/textwindowaccessibility.cxx | 2271 |
1 files changed, 2271 insertions, 0 deletions
diff --git a/accessibility/source/extended/textwindowaccessibility.cxx b/accessibility/source/extended/textwindowaccessibility.cxx new file mode 100644 index 000000000..218ba33e6 --- /dev/null +++ b/accessibility/source/extended/textwindowaccessibility.cxx @@ -0,0 +1,2271 @@ +/* -*- 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/accessibleeventnotifier.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/txtattr.hxx> +#include <vcl/window.hxx> +#include <tools/diagnose_ex.h> +#include <toolkit/helper/convert.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( + static_cast< ::cppu::OWeakObject * >(this), + nEventId, rNewValue, rOldValue) ); +} + +// virtual +css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL +Paragraph::getAccessibleContext() +{ + checkDisposed(); + return this; +} + +// virtual +::sal_Int32 SAL_CALL Paragraph::getAccessibleChildCount() +{ + checkDisposed(); + return 0; +} + +// virtual +css::uno::Reference< css::accessibility::XAccessible > SAL_CALL +Paragraph::getAccessibleChild(::sal_Int32) +{ + checkDisposed(); + throw css::lang::IndexOutOfBoundsException( + "textwindowaccessibility.cxx:" + " Paragraph::getAccessibleChild", + static_cast< css::uno::XWeak * >(this)); +} + +// virtual +css::uno::Reference< css::accessibility::XAccessible > SAL_CALL +Paragraph::getAccessibleParent() +{ + checkDisposed(); + return m_xDocument->getAccessible(); +} + +// virtual +::sal_Int32 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 +css::uno::Reference< css::accessibility::XAccessibleStateSet > +SAL_CALL Paragraph::getAccessibleStateSet() +{ + checkDisposed(); + + // FIXME Notification of changes (STATE_CHANGED) missing when + // m_rView.IsReadOnly() changes: + return new ::utl::AccessibleStateSetHelper( + 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( + "textwindowaccessibility.cxx:" + " Paragraph::getTextAtLineWithCaret", + static_cast< css::uno::XWeak * >( this ), 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( + static_cast< ::cppu::OWeakObject * >(this))); + } + 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(), static_cast< css::uno::XWeak * >(this)); +} + +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_nViewOffset(0), + m_nViewHeight(0), + m_nVisibleBeginOffset(0), + m_nSelectionFirstPara(-1), + m_nSelectionFirstPos(-1), + m_nSelectionLastPara(-1), + m_nSelectionLastPos(-1), + m_bSelectionChangedNotification(false) +{} + +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: + Paragraphs::iterator aPara(m_xParagraphs->begin() + + pParagraph->getNumber()); + return aPara < m_aVisibleBegin || aPara >= m_aVisibleEnd + ? -1 : static_cast< ::sal_Int32 >(aPara - m_aVisibleBegin); + // 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 + = (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::ENABLED) + | (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::SENSITIVE) + | (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::FOCUSABLE) + | (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::MULTI_LINE); + if (!m_rView.IsReadOnly()) + nState |= (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::EDITABLE); + Paragraphs::iterator aPara(m_xParagraphs->begin() + + pParagraph->getNumber()); + if (aPara >= m_aVisibleBegin && aPara < m_aVisibleEnd) + { + nState + |= (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::VISIBLE) + | (static_cast< ::sal_Int64 >(1) + << css::accessibility::AccessibleStateType::SHOWING); + if (aPara == m_aFocused) + nState |= (static_cast< ::sal_Int64 >(1) + << 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: + Paragraphs::iterator aPara(m_xParagraphs->begin() + + pParagraph->getNumber()); + auto lAddHeight = [](const sal_Int32& rSum, const ParagraphInfo& rParagraph) { + return rSum + rParagraph.getHeight(); }; + ::sal_Int32 nPos; + if (aPara < m_aVisibleBegin) + nPos = std::accumulate(m_xParagraphs->begin(), aPara, sal_Int32(0), lAddHeight); + else + nPos = std::accumulate(m_aVisibleBegin, aPara, m_nViewOffset - m_nVisibleBeginOffset, lAddHeight); + + Point aOrig(0, 0); + if (bAbsolute) + aOrig = 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(), aPara->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( + "textwindowaccessibility.cxx:" + " Document::retrieveCharacterAttributes", + static_cast< css::uno::XWeak * >(this)); + css::awt::Rectangle aBounds( 0, 0, 0, 0 ); + if ( nIndex == nLength ) + { + aBounds = AWTRectangle( + 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( + "textwindowaccessibility.cxx:" + " Document::retrieveCharacterAttributes", + static_cast< css::uno::XWeak * >(this)); + + + // 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::unique_ptr<sal_Int32[]> pIndices( new sal_Int32[nLength] ); + std::iota(&pIndices[0], &pIndices[nLength], 0); + std::sort(&pIndices[0], &pIndices[nLength], + [&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[0], &pIndices[nLength], std::back_inserter(aNewValues), + [&aRes](const sal_Int32 nIdx) { 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; + aPropVal.Name = "CharColor"; + aPropVal.Handle = -1; + aPropVal.Value = mapFontColor( pColor->GetColor() ); + aPropVal.State = css::beans::PropertyState_DIRECT_VALUE; + aRunAttrSeq[ aPropVal.Name ] = aPropVal; + } + if ( pWeight ) + { + css::beans::PropertyValue aPropVal; + aPropVal.Name = "CharWeight"; + aPropVal.Handle = -1; + aPropVal.Value = mapFontWeight( pWeight->getFontWeight() ); + aPropVal.State = css::beans::PropertyState_DIRECT_VALUE; + aRunAttrSeq[ aPropVal.Name ] = aPropVal; + } + if ( !RequestedAttributes.hasElements() ) + { + rRunAttrSeq = 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( + "textwindowaccessibility.cxx:" + " Document::retrieveRunAttributes", + static_cast< css::uno::XWeak * >( this ) ); + + 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( + "textwindowaccessibility.cxx:" + " Document::changeParagraphText", + static_cast< css::uno::XWeak * >(this)); + 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( + "textwindowaccessibility.cxx:" + " Document::copyParagraphText", + static_cast< css::uno::XWeak * >(this)); + 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( + "textwindowaccessibility.cxx:" + " Document::changeParagraphAttributes", + static_cast< css::uno::XWeak * >(this)); + + // 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( + "textwindowaccessibility.cxx:" + " Document::changeParagraphSelection", + static_cast< css::uno::XWeak * >(this)); + 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( + "textwindowaccessibility.cxx:" + " Document::retrieveParagraphLineBoundary", + static_cast< css::uno::XWeak * >( this ) ); + ::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( + "textwindowaccessibility.cxx:" + " Document::retrieveParagraphBoundaryOfLine", + static_cast< css::uno::XWeak * >( this ) ); + ::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(); + + Paragraphs::iterator aPara( m_xParagraphs->begin() + pParagraph->getNumber() ); + + if ( aPara > m_aVisibleBegin && aPara < m_aVisibleEnd ) + { + css::uno::Sequence< css::uno::Reference< css::uno::XInterface > > aSequence { getAccessibleChild( aPara - 1 ) }; + css::accessibility::AccessibleRelation aRelation( css::accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM, aSequence ); + pRelationSetHelper->AddRelation( aRelation ); + } + + if ( aPara >= m_aVisibleBegin && aPara < m_aVisibleEnd -1 ) + { + css::uno::Sequence< css::uno::Reference< css::uno::XInterface > > aSequence { getAccessibleChild( aPara + 1 ) }; + css::accessibility::AccessibleRelation aRelation( css::accessibility::AccessibleRelationType::CONTENT_FLOWS_TO, aSequence ); + pRelationSetHelper->AddRelation( aRelation ); + } + + return pRelationSetHelper; +} + +// virtual +::sal_Int32 SAL_CALL Document::getAccessibleChildCount() +{ + ::comphelper::OExternalLockGuard aGuard(this); + init(); + return m_aVisibleEnd - m_aVisibleBegin; +} + +// virtual +css::uno::Reference< css::accessibility::XAccessible > SAL_CALL +Document::getAccessibleChild(::sal_Int32 i) +{ + ::comphelper::OExternalLockGuard aGuard(this); + init(); + if (i < 0 || i >= m_aVisibleEnd - m_aVisibleBegin) + throw css::lang::IndexOutOfBoundsException( + "textwindowaccessibility.cxx:" + " Document::getAccessibleChild", + static_cast< css::uno::XWeak * >(this)); + return getAccessibleChild(m_aVisibleBegin + + static_cast< Paragraphs::size_type >(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); + init(); + 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(m_aVisibleBegin); aIt != m_aVisibleEnd; + ++aIt) + { + nPos += aIt->getHeight(); // XXX numeric overflow + if (nOffset < nPos) + return getAccessibleChild(aIt); + } + } + return nullptr; +} +void Document::FillAccessibleStateSet( utl::AccessibleStateSetHelper& rStateSet ) +{ + VCLXAccessibleComponent::FillAccessibleStateSet( rStateSet ); + if (!m_rView.IsReadOnly()) + rStateSet.AddState( 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::uno::XInterface > > 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(); + if (m_xParagraphs != nullptr) + disposeParagraphs(); + VCLXAccessibleComponent::disposing(); +} + +// virtual +void Document::Notify(::SfxBroadcaster &, ::SfxHint const & rHint) +{ + const TextHint* pTextHint = dynamic_cast<const TextHint*>(&rHint); + if (!pTextHint) + return; + + ::TextHint const & rTextHint = *pTextHint; + switch (rTextHint.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; + + 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( + m_aVisibleBegin); + Paragraphs::iterator aOldVisibleEnd(m_aVisibleEnd); + + determineVisibleRange(); + + notifyVisibleRangeChanges(aOldVisibleBegin, + aOldVisibleEnd, + m_xParagraphs->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(m_aVisibleBegin); + Paragraphs::iterator aOldVisibleEnd(m_aVisibleEnd); + + determineVisibleRange(); + + notifyVisibleRangeChanges(aOldVisibleBegin, aOldVisibleEnd, + m_xParagraphs->end()); + } + break; + } + case VclEventId::WindowGetFocus: + { + ::osl::MutexGuard aInternalGuard(GetMutex()); + if (!isAlive()) + break; + //to enable the PARAGRAPH to get focus for multiline edit + ::sal_Int32 count = getAccessibleChildCount(); + bool bEmpty = m_aFocused == m_aVisibleEnd && count == 1; + if ((m_aFocused >= m_aVisibleBegin && m_aFocused < m_aVisibleEnd) || bEmpty) + { + Paragraphs::iterator aTemp = bEmpty ? m_aVisibleBegin : m_aFocused; + ::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_Int32 count = getAccessibleChildCount(); + bool bEmpty = m_aFocused == m_aVisibleEnd && count == 1; + if ((m_aFocused >= m_aVisibleBegin && m_aFocused < m_aVisibleEnd) || bEmpty) + { + Paragraphs::iterator aTemp = bEmpty ? m_aVisibleBegin : m_aFocused; + ::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; + } +} + +void Document::init() +{ + if (m_xParagraphs != nullptr) + return; + + const ::sal_uInt32 nCount = m_rEngine.GetParagraphCount(); + m_xParagraphs.reset(new Paragraphs); + m_xParagraphs->reserve(static_cast< Paragraphs::size_type >(nCount)); + // numeric overflow is harmless here + for (::sal_uInt32 i = 0; i < nCount; ++i) + m_xParagraphs->push_back(ParagraphInfo(static_cast< ::sal_Int32 >( + m_rEngine.GetTextHeight(i)))); + // XXX numeric overflow + 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_nSelectionFirstPara = -1; + m_nSelectionFirstPos = -1; + m_nSelectionLastPara = -1; + m_nSelectionLastPos = -1; + m_aFocused = m_xParagraphs->end(); + m_bSelectionChangedNotification = false; + m_aEngineListener.startListening(m_rEngine); + m_aViewListener.startListening(*m_rView.GetWindow()); +} + +::rtl::Reference< Paragraph > +Document::getParagraph(Paragraphs::iterator const & rIt) +{ + return static_cast< Paragraph * >( + css::uno::Reference< css::accessibility::XAccessible >( + rIt->getParagraph()).get()); +} + +css::uno::Reference< css::accessibility::XAccessible > +Document::getAccessibleChild(Paragraphs::iterator const & rIt) +{ + css::uno::Reference< css::accessibility::XAccessible > xParagraph( + rIt->getParagraph()); + if (!xParagraph.is()) + { + xParagraph = new Paragraph(this, rIt - m_xParagraphs->begin()); + rIt->setParagraph(xParagraph); + } + return xParagraph; +} + +void Document::determineVisibleRange() +{ + Paragraphs::iterator const aEnd = m_xParagraphs->end(); + + m_aVisibleBegin = aEnd; + m_aVisibleEnd = aEnd; + m_nVisibleBeginOffset = 0; + + ::sal_Int32 nPos = 0; + for (Paragraphs::iterator aIt = m_xParagraphs->begin(); m_aVisibleEnd == aEnd && aIt != aEnd; ++aIt) + { + ::sal_Int32 const nOldPos = nPos; + nPos += aIt->getHeight(); // XXX numeric overflow + if (m_aVisibleBegin == aEnd) + { + if (nPos >= m_nViewOffset) + { + m_aVisibleBegin = aIt; + m_nVisibleBeginOffset = m_nViewOffset - nOldPos; + } + } + else + { + if (nPos >= m_nViewOffset + m_nViewHeight) // XXX numeric overflow + { + m_aVisibleEnd = aIt; + } + } + } + + SAL_WARN_IF( + !((m_aVisibleBegin == m_xParagraphs->end() && m_aVisibleEnd == m_xParagraphs->end() && m_nVisibleBeginOffset == 0) + || (m_aVisibleBegin < m_aVisibleEnd && 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. + for (Paragraphs::iterator aIt(rOldVisibleBegin); aIt != rOldVisibleEnd; + ++aIt) + { + if (aIt != rInserted + && (aIt < m_aVisibleBegin || aIt >= m_aVisibleEnd)) + NotifyAccessibleEvent( + css::accessibility::AccessibleEventId:: + CHILD, + css::uno::Any(getAccessibleChild(aIt)), + css::uno::Any()); + } + for (Paragraphs::iterator aIt(m_aVisibleBegin); aIt != m_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() +{ + 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_xParagraphs->size() && "bad SfxHintId::TextParaInserted event"); + + // Save the values of old iterators (the iterators themselves + // will get invalidated), and adjust the old values so that they + // reflect the insertion of the new paragraph: + Paragraphs::size_type nOldVisibleBegin + = m_aVisibleBegin - m_xParagraphs->begin(); + Paragraphs::size_type nOldVisibleEnd + = m_aVisibleEnd - m_xParagraphs->begin(); + Paragraphs::size_type nOldFocused + = m_aFocused - m_xParagraphs->begin(); + 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_xParagraphs->insert( + m_xParagraphs->begin() + n, + ParagraphInfo(static_cast< ::sal_Int32 >( + m_rEngine.GetTextHeight(n))))); + // XXX numeric overflow (2x) + + determineVisibleRange(); + m_aFocused = m_xParagraphs->begin() + nOldFocused; + + for (Paragraphs::iterator aIt(aIns);;) + { + ++aIt; + if (aIt == m_xParagraphs->end()) + break; + ::rtl::Reference< Paragraph > xParagraph( + getParagraph(aIt)); + if (xParagraph.is()) + xParagraph->numberChanged(true); + } + + notifyVisibleRangeChanges( + m_xParagraphs->begin() + nOldVisibleBegin, + m_xParagraphs->begin() + nOldVisibleEnd, aIns); + break; + } + case SfxHintId::TextParaRemoved: + { + ::sal_uInt32 n = static_cast< ::sal_uInt32 >( aHint.GetValue() ); + if (n == TEXT_PARA_ALL) + { + for (Paragraphs::iterator aIt(m_aVisibleBegin); + aIt != m_aVisibleEnd; ++aIt) + { + NotifyAccessibleEvent( + css::accessibility::AccessibleEventId:: + CHILD, + css::uno::Any(getAccessibleChild(aIt)), + css::uno::Any()); + } + disposeParagraphs(); + m_xParagraphs->clear(); + determineVisibleRange(); + m_nSelectionFirstPara = -1; + m_nSelectionFirstPos = -1; + m_nSelectionLastPara = -1; + m_nSelectionLastPos = -1; + m_aFocused = m_xParagraphs->end(); + } + else + { + assert(n < m_xParagraphs->size() && "Bad SfxHintId::TextParaRemoved event"); + + Paragraphs::iterator aIt(m_xParagraphs->begin() + n); + // numeric overflow cannot occur + + // Save the values of old iterators (the iterators + // themselves will get invalidated), and adjust the old + // values so that they reflect the removal of the paragraph: + Paragraphs::size_type nOldVisibleBegin + = m_aVisibleBegin - m_xParagraphs->begin(); + Paragraphs::size_type nOldVisibleEnd + = m_aVisibleEnd - m_xParagraphs->begin(); + bool bWasVisible + = nOldVisibleBegin <= n && n < nOldVisibleEnd; + Paragraphs::size_type nOldFocused + = m_aFocused - m_xParagraphs->begin(); + bool bWasFocused = aIt == m_aFocused; + 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); + css::uno::WeakReference< + css::accessibility::XAccessible > xWeak( + aIt->getParagraph()); + aIt = m_xParagraphs->erase(aIt); + + determineVisibleRange(); + m_aFocused = bWasFocused ? m_xParagraphs->end() + : m_xParagraphs->begin() + nOldFocused; + + for (; aIt != m_xParagraphs->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()); + + css::uno::Reference< css::lang::XComponent > xComponent( + xWeak.get(), css::uno::UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + + notifyVisibleRangeChanges( + m_xParagraphs->begin() + nOldVisibleBegin, + m_xParagraphs->begin() + nOldVisibleEnd, + m_xParagraphs->end()); + } + break; + } + case SfxHintId::TextFormatPara: + { + ::sal_uInt32 n = static_cast< ::sal_uInt32 >( aHint.GetValue() ); + assert(n < m_xParagraphs->size() && "Bad SfxHintId::TextFormatPara event"); + + (*m_xParagraphs)[static_cast< Paragraphs::size_type >(n)]. + changeHeight(static_cast< ::sal_Int32 >( + m_rEngine.GetTextHeight(n))); + // XXX numeric overflow + Paragraphs::iterator aOldVisibleBegin(m_aVisibleBegin); + Paragraphs::iterator aOldVisibleEnd(m_aVisibleEnd); + determineVisibleRange(); + notifyVisibleRangeChanges(aOldVisibleBegin, aOldVisibleEnd, + m_xParagraphs->end()); + + if (n < m_xParagraphs->size()) + { + Paragraphs::iterator aIt(m_xParagraphs->begin() + n); + ::rtl::Reference< Paragraph > xParagraph(getParagraph(aIt)); + if (xParagraph.is()) + xParagraph->textChanged(); + } + break; + } + default: + SAL_WARN("accessibility", "bad buffered hint"); + break; + } + } + 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_xParagraphs->begin(), m_aVisibleEnd); + + Paragraphs::iterator aEnd(m_xParagraphs->begin()); + size_t nEndDistance = std::min<size_t>(end + 1, nAvailDistance); + std::advance(aEnd, nEndDistance); + + Paragraphs::iterator aIt(m_xParagraphs->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_xParagraphs->size() && + rSelection.GetEnd().GetPara() < m_xParagraphs->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(m_xParagraphs->begin() + nNewLastPara); + if (m_aFocused != m_xParagraphs->end() && m_aFocused != aIt + && m_aFocused >= m_aVisibleBegin && m_aFocused < m_aVisibleEnd) + { + ::rtl::Reference< Paragraph > xParagraph(getParagraph(m_aFocused)); + 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 >= m_aVisibleBegin && aIt < m_aVisibleEnd + && (aIt != m_aFocused + || 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_Int32 count = getAccessibleChildCount(); + bool bEmpty = count > 1; + //if (aIt != m_aFocused) + if (aIt != m_aFocused && 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_aFocused = 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_xParagraphs) + { + css::uno::Reference< css::lang::XComponent > xComponent( + paragraph.getParagraph().get(), css::uno::UNO_QUERY); + 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: */ |