3346 lines
119 KiB
C++
3346 lines
119 KiB
C++
/* -*- 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 <rtl/ref.hxx>
|
|
#include <cppuhelper/weakref.hxx>
|
|
#include <utility>
|
|
#include <vcl/window.hxx>
|
|
#include <svx/svdmodel.hxx>
|
|
#include <svx/unomod.hxx>
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <list>
|
|
#include <vector>
|
|
#include <accmap.hxx>
|
|
#include "acccontext.hxx"
|
|
#include "accdoc.hxx"
|
|
#include <strings.hrc>
|
|
#include "accpreview.hxx"
|
|
#include "accpage.hxx"
|
|
#include "accpara.hxx"
|
|
#include "accheaderfooter.hxx"
|
|
#include "accfootnote.hxx"
|
|
#include "acctextframe.hxx"
|
|
#include "accgraphic.hxx"
|
|
#include "accembedded.hxx"
|
|
#include "acccell.hxx"
|
|
#include "acctable.hxx"
|
|
#include <fesh.hxx>
|
|
#include <istype.hxx>
|
|
#include <rootfrm.hxx>
|
|
#include <txtfrm.hxx>
|
|
#include <hffrm.hxx>
|
|
#include <ftnfrm.hxx>
|
|
#include <cellfrm.hxx>
|
|
#include <tabfrm.hxx>
|
|
#include <pagefrm.hxx>
|
|
#include <flyfrm.hxx>
|
|
#include <ndtyp.hxx>
|
|
#include <IDocumentDrawModelAccess.hxx>
|
|
#include <svx/AccessibleShapeInfo.hxx>
|
|
#include <svx/ShapeTypeHandler.hxx>
|
|
#include <svx/SvxShapeTypes.hxx>
|
|
#include <svx/svdpage.hxx>
|
|
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
|
|
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
|
|
#include <com/sun/star/accessibility/AccessibleRole.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <com/sun/star/document/XShapeEventBroadcaster.hpp>
|
|
#include <cppuhelper/implbase.hxx>
|
|
#include <comphelper/interfacecontainer4.hxx>
|
|
#include <pagepreviewlayout.hxx>
|
|
#include <dcontact.hxx>
|
|
#include <svx/svdmark.hxx>
|
|
#include <doc.hxx>
|
|
#include <drawdoc.hxx>
|
|
#include <pam.hxx>
|
|
#include <ndtxt.hxx>
|
|
#include <dflyobj.hxx>
|
|
#include <prevwpage.hxx>
|
|
#include <calbck.hxx>
|
|
#include <undobj.hxx>
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
#include <tools/debug.hxx>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::accessibility;
|
|
using namespace ::sw::access;
|
|
|
|
class SwAccessibleContextMap_Impl
|
|
{
|
|
public:
|
|
typedef const SwFrame * key_type;
|
|
typedef unotools::WeakReference < SwAccessibleContext > mapped_type;
|
|
typedef std::pair<const key_type,mapped_type> value_type;
|
|
typedef std::unordered_map<key_type, mapped_type>::iterator iterator;
|
|
typedef std::unordered_map<key_type, mapped_type>::const_iterator const_iterator;
|
|
private:
|
|
std::unordered_map <key_type, mapped_type> maMap;
|
|
public:
|
|
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
bool mbLocked;
|
|
#endif
|
|
|
|
SwAccessibleContextMap_Impl()
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
: mbLocked( false )
|
|
#endif
|
|
{}
|
|
|
|
iterator begin() { return maMap.begin(); }
|
|
iterator end() { return maMap.end(); }
|
|
bool empty() const { return maMap.empty(); }
|
|
void clear() { maMap.clear(); }
|
|
iterator find(const key_type& key) { return maMap.find(key); }
|
|
template<class... Args>
|
|
std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); }
|
|
iterator erase(const_iterator const & pos) { return maMap.erase(pos); }
|
|
};
|
|
|
|
namespace {
|
|
|
|
class SwDrawModellListener_Impl : public SfxListener,
|
|
public ::cppu::WeakImplHelper< document::XShapeEventBroadcaster >
|
|
{
|
|
mutable std::mutex maListenerMutex;
|
|
::comphelper::OInterfaceContainerHelper4<css::document::XEventListener> maEventListeners;
|
|
std::unordered_multimap<css::uno::Reference< css::drawing::XShape >, css::uno::Reference< css::document::XShapeEventListener >> maShapeListeners;
|
|
SdrModel *mpDrawModel;
|
|
protected:
|
|
virtual ~SwDrawModellListener_Impl() override;
|
|
|
|
public:
|
|
explicit SwDrawModellListener_Impl( SdrModel& rDrawModel );
|
|
|
|
// css::document::XEventBroadcaster
|
|
virtual void SAL_CALL addEventListener( const uno::Reference< document::XEventListener >& xListener ) override;
|
|
virtual void SAL_CALL removeEventListener( const uno::Reference< document::XEventListener >& xListener ) override;
|
|
// css::document::XShapeEventBroadcaster
|
|
virtual void SAL_CALL addShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override;
|
|
virtual void SAL_CALL removeShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override;
|
|
|
|
virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
|
|
void Dispose();
|
|
};
|
|
|
|
}
|
|
|
|
SwDrawModellListener_Impl::SwDrawModellListener_Impl( SdrModel& rDrawModel ) :
|
|
mpDrawModel( &rDrawModel )
|
|
{
|
|
StartListening( *mpDrawModel );
|
|
}
|
|
|
|
SwDrawModellListener_Impl::~SwDrawModellListener_Impl()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
void SAL_CALL SwDrawModellListener_Impl::addEventListener( const uno::Reference< document::XEventListener >& xListener )
|
|
{
|
|
std::unique_lock g(maListenerMutex);
|
|
maEventListeners.addInterface( g, xListener );
|
|
}
|
|
|
|
void SAL_CALL SwDrawModellListener_Impl::removeEventListener( const uno::Reference< document::XEventListener >& xListener )
|
|
{
|
|
std::unique_lock g(maListenerMutex);
|
|
maEventListeners.removeInterface( g, xListener );
|
|
}
|
|
|
|
void SAL_CALL SwDrawModellListener_Impl::addShapeEventListener(
|
|
const css::uno::Reference< css::drawing::XShape >& xShape,
|
|
const uno::Reference< document::XShapeEventListener >& xListener )
|
|
{
|
|
assert(xShape.is() && "no shape?");
|
|
std::unique_lock aGuard(maListenerMutex);
|
|
maShapeListeners.emplace(xShape, xListener);
|
|
}
|
|
|
|
void SAL_CALL SwDrawModellListener_Impl::removeShapeEventListener(
|
|
const css::uno::Reference< css::drawing::XShape >& xShape,
|
|
const uno::Reference< document::XShapeEventListener >& xListener )
|
|
{
|
|
std::unique_lock aGuard(maListenerMutex);
|
|
auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape);
|
|
for (auto it = itBegin; it != itEnd; ++it)
|
|
if (it->second == xListener)
|
|
{
|
|
maShapeListeners.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SwDrawModellListener_Impl::Notify( SfxBroadcaster& /*rBC*/,
|
|
const SfxHint& rHint )
|
|
{
|
|
// do not broadcast notifications for writer fly frames, because there
|
|
// are no shapes that need to know about them.
|
|
if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint)
|
|
return;
|
|
const SdrHint *pSdrHint = static_cast<const SdrHint*>( &rHint );
|
|
const SdrObject* pObj = pSdrHint->GetObject();
|
|
if (pObj &&
|
|
( dynamic_cast< const SwFlyDrawObj* >(pObj) ||
|
|
dynamic_cast< const SwVirtFlyDrawObj* >(pObj) ||
|
|
pObj->GetObjIdentifier() == SdrObjKind::NewFrame ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
OSL_ENSURE( mpDrawModel, "draw model listener is disposed" );
|
|
if( !mpDrawModel )
|
|
return;
|
|
|
|
document::EventObject aEvent;
|
|
if( !SvxUnoDrawMSFactory::createEvent( mpDrawModel, pSdrHint, aEvent ) )
|
|
return;
|
|
|
|
{
|
|
std::unique_lock g(maListenerMutex);
|
|
::comphelper::OInterfaceIteratorHelper4 aIter( g, maEventListeners );
|
|
g.unlock();
|
|
while( aIter.hasMoreElements() )
|
|
{
|
|
try
|
|
{
|
|
aIter.next()->notifyEvent( aEvent );
|
|
}
|
|
catch( uno::RuntimeException const & )
|
|
{
|
|
TOOLS_WARN_EXCEPTION("sw.a11y", "Runtime exception caught while notifying shape");
|
|
}
|
|
}
|
|
}
|
|
|
|
// right now, we're only handling the specific event necessary to fix this performance problem
|
|
if (pSdrHint->GetKind() == SdrHintKind::ObjectChange)
|
|
{
|
|
auto pSdrObject = const_cast<SdrObject*>(pSdrHint->GetObject());
|
|
uno::Reference<drawing::XShape> xShape(pSdrObject->getUnoShape(), uno::UNO_QUERY);
|
|
std::unique_lock aGuard(maListenerMutex);
|
|
auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape);
|
|
for (auto it = itBegin; it != itEnd; ++it)
|
|
it->second->notifyShapeEvent(aEvent);
|
|
}
|
|
}
|
|
|
|
void SwDrawModellListener_Impl::Dispose()
|
|
{
|
|
if (mpDrawModel != nullptr) {
|
|
EndListening( *mpDrawModel );
|
|
}
|
|
mpDrawModel = nullptr;
|
|
}
|
|
|
|
typedef std::pair < const SdrObject *, ::rtl::Reference < ::accessibility::AccessibleShape > > SwAccessibleObjShape_Impl;
|
|
|
|
class SwAccessibleShapeMap_Impl
|
|
{
|
|
public:
|
|
|
|
typedef const SdrObject * key_type;
|
|
typedef unotools::WeakReference<::accessibility::AccessibleShape> mapped_type;
|
|
typedef std::pair<const key_type,mapped_type> value_type;
|
|
typedef std::map<key_type, mapped_type>::iterator iterator;
|
|
typedef std::map<key_type, mapped_type>::const_iterator const_iterator;
|
|
|
|
private:
|
|
|
|
::accessibility::AccessibleShapeTreeInfo maInfo;
|
|
std::map<key_type, mapped_type> maMap;
|
|
|
|
public:
|
|
|
|
explicit SwAccessibleShapeMap_Impl( SwAccessibleMap const *pMap )
|
|
{
|
|
maInfo.SetSdrView( pMap->GetShell()->GetDrawView() );
|
|
maInfo.SetWindow( pMap->GetShell()->GetWin() );
|
|
maInfo.SetViewForwarder( pMap );
|
|
uno::Reference < document::XShapeEventBroadcaster > xModelBroadcaster =
|
|
new SwDrawModellListener_Impl(
|
|
pMap->GetShell()->getIDocumentDrawModelAccess().GetOrCreateDrawModel() );
|
|
maInfo.SetModelBroadcaster( xModelBroadcaster );
|
|
}
|
|
|
|
~SwAccessibleShapeMap_Impl();
|
|
|
|
const ::accessibility::AccessibleShapeTreeInfo& GetInfo() const { return maInfo; }
|
|
|
|
std::unique_ptr<SwAccessibleObjShape_Impl[]> Copy( size_t& rSize,
|
|
const SwFEShell *pFESh,
|
|
SwAccessibleObjShape_Impl **pSelShape ) const;
|
|
|
|
iterator end() { return maMap.end(); }
|
|
const_iterator cbegin() const { return maMap.cbegin(); }
|
|
const_iterator cend() const { return maMap.cend(); }
|
|
bool empty() const { return maMap.empty(); }
|
|
iterator find(const key_type& key) { return maMap.find(key); }
|
|
template<class... Args>
|
|
std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); }
|
|
iterator erase(const_iterator const & pos) { return maMap.erase(pos); }
|
|
};
|
|
|
|
SwAccessibleShapeMap_Impl::~SwAccessibleShapeMap_Impl()
|
|
{
|
|
uno::Reference < document::XEventBroadcaster > xBrd( maInfo.GetModelBroadcaster() );
|
|
if( xBrd.is() )
|
|
static_cast < SwDrawModellListener_Impl * >( xBrd.get() )->Dispose();
|
|
}
|
|
|
|
std::unique_ptr<SwAccessibleObjShape_Impl[]>
|
|
SwAccessibleShapeMap_Impl::Copy(
|
|
size_t& rSize, const SwFEShell *pFESh,
|
|
SwAccessibleObjShape_Impl **pSelStart ) const
|
|
{
|
|
std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes;
|
|
SwAccessibleObjShape_Impl *pSelShape = nullptr;
|
|
|
|
size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0;
|
|
rSize = maMap.size();
|
|
|
|
if( rSize > 0 )
|
|
{
|
|
pShapes.reset(new SwAccessibleObjShape_Impl[rSize]);
|
|
|
|
SwAccessibleObjShape_Impl *pShape = pShapes.get();
|
|
pSelShape = &(pShapes[rSize]);
|
|
for( const auto& rEntry : maMap )
|
|
{
|
|
const SdrObject *pObj = rEntry.first;
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( rEntry.second );
|
|
if( nSelShapes && pFESh && pFESh->IsObjSelected( *pObj ) )
|
|
{
|
|
// selected objects are inserted from the back
|
|
--pSelShape;
|
|
pSelShape->first = pObj;
|
|
pSelShape->second = xAcc.get();
|
|
--nSelShapes;
|
|
}
|
|
else
|
|
{
|
|
pShape->first = pObj;
|
|
pShape->second = xAcc.get();
|
|
++pShape;
|
|
}
|
|
}
|
|
assert(pSelShape == pShape);
|
|
}
|
|
|
|
if( pSelStart )
|
|
*pSelStart = pSelShape;
|
|
|
|
return pShapes;
|
|
}
|
|
|
|
struct SwAccessibleEvent_Impl
|
|
{
|
|
public:
|
|
enum EventType { CARET_OR_STATES,
|
|
INVALID_CONTENT,
|
|
POS_CHANGED,
|
|
CHILD_POS_CHANGED,
|
|
SHAPE_SELECTION,
|
|
DISPOSE,
|
|
INVALID_ATTR };
|
|
|
|
private:
|
|
SwRect maOldBox; // the old bounds for CHILD_POS_CHANGED
|
|
// and POS_CHANGED
|
|
unotools::WeakReference < SwAccessibleContext > mxAcc; // The object that fires the event
|
|
SwAccessibleChild maFrameOrObj; // the child for CHILD_POS_CHANGED and
|
|
// the same as xAcc for any other
|
|
// event type
|
|
EventType meType; // The event type
|
|
AccessibleStates mnStates; // check states or update caret pos
|
|
|
|
public:
|
|
const SwFrame* mpParentFrame; // The object that fires the event
|
|
bool IsNoXaccParentFrame() const
|
|
{
|
|
return CHILD_POS_CHANGED == meType && mpParentFrame != nullptr;
|
|
}
|
|
|
|
public:
|
|
SwAccessibleEvent_Impl( EventType eT,
|
|
SwAccessibleContext *pA,
|
|
SwAccessibleChild aFrameOrObj )
|
|
: mxAcc( pA ),
|
|
maFrameOrObj(std::move( aFrameOrObj )),
|
|
meType( eT ),
|
|
mnStates( AccessibleStates::NONE ),
|
|
mpParentFrame( nullptr )
|
|
{}
|
|
|
|
SwAccessibleEvent_Impl( EventType eT,
|
|
SwAccessibleChild aFrameOrObj )
|
|
: maFrameOrObj(std::move( aFrameOrObj )),
|
|
meType( eT ),
|
|
mnStates( AccessibleStates::NONE ),
|
|
mpParentFrame( nullptr )
|
|
{
|
|
assert(SwAccessibleEvent_Impl::DISPOSE == meType &&
|
|
"wrong event constructor, DISPOSE only");
|
|
}
|
|
|
|
explicit SwAccessibleEvent_Impl( EventType eT )
|
|
: meType( eT ),
|
|
mnStates( AccessibleStates::NONE ),
|
|
mpParentFrame( nullptr )
|
|
{
|
|
assert(SwAccessibleEvent_Impl::SHAPE_SELECTION == meType &&
|
|
"wrong event constructor, SHAPE_SELECTION only" );
|
|
}
|
|
|
|
SwAccessibleEvent_Impl( EventType eT,
|
|
SwAccessibleContext *pA,
|
|
SwAccessibleChild aFrameOrObj,
|
|
const SwRect& rR )
|
|
: maOldBox( rR ),
|
|
mxAcc( pA ),
|
|
maFrameOrObj(std::move( aFrameOrObj )),
|
|
meType( eT ),
|
|
mnStates( AccessibleStates::NONE ),
|
|
mpParentFrame( nullptr )
|
|
{
|
|
assert((SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType ||
|
|
SwAccessibleEvent_Impl::POS_CHANGED == meType) &&
|
|
"wrong event constructor, (CHILD_)POS_CHANGED only" );
|
|
}
|
|
|
|
SwAccessibleEvent_Impl( EventType eT,
|
|
SwAccessibleContext *pA,
|
|
SwAccessibleChild aFrameOrObj,
|
|
const AccessibleStates _nStates )
|
|
: mxAcc( pA ),
|
|
maFrameOrObj(std::move( aFrameOrObj )),
|
|
meType( eT ),
|
|
mnStates( _nStates ),
|
|
mpParentFrame( nullptr )
|
|
{
|
|
assert( SwAccessibleEvent_Impl::CARET_OR_STATES == meType &&
|
|
"wrong event constructor, CARET_OR_STATES only" );
|
|
}
|
|
|
|
SwAccessibleEvent_Impl( EventType eT, const SwFrame *pParentFrame,
|
|
SwAccessibleChild aFrameOrObj, const SwRect& rR ) :
|
|
maOldBox( rR ),
|
|
maFrameOrObj(std::move( aFrameOrObj )),
|
|
meType( eT ),
|
|
mnStates( AccessibleStates::NONE ),
|
|
mpParentFrame( pParentFrame )
|
|
{
|
|
assert( SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType &&
|
|
"wrong event constructor, CHILD_POS_CHANGED only" );
|
|
}
|
|
|
|
// <SetType(..)> only used in method <SwAccessibleMap::AppendEvent(..)>
|
|
void SetType( EventType eT )
|
|
{
|
|
meType = eT;
|
|
}
|
|
EventType GetType() const
|
|
{
|
|
return meType;
|
|
}
|
|
|
|
::rtl::Reference < SwAccessibleContext > GetContext() const
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAccImpl( mxAcc );
|
|
return xAccImpl;
|
|
}
|
|
|
|
const SwRect& GetOldBox() const
|
|
{
|
|
return maOldBox;
|
|
}
|
|
// <SetOldBox(..)> only used in method <SwAccessibleMap::AppendEvent(..)>
|
|
void SetOldBox( const SwRect& rOldBox )
|
|
{
|
|
maOldBox = rOldBox;
|
|
}
|
|
|
|
const SwAccessibleChild& GetFrameOrObj() const
|
|
{
|
|
return maFrameOrObj;
|
|
}
|
|
|
|
// <SetStates(..)> only used in method <SwAccessibleMap::AppendEvent(..)>
|
|
void SetStates( AccessibleStates _nStates )
|
|
{
|
|
mnStates |= _nStates;
|
|
}
|
|
|
|
bool IsUpdateCursorPos() const
|
|
{
|
|
return bool(mnStates & AccessibleStates::CARET);
|
|
}
|
|
bool IsInvalidateStates() const
|
|
{
|
|
return bool(mnStates & (AccessibleStates::EDITABLE | AccessibleStates::OPAQUE));
|
|
}
|
|
bool IsInvalidateRelation() const
|
|
{
|
|
return bool(mnStates & (AccessibleStates::RELATION_FROM | AccessibleStates::RELATION_TO));
|
|
}
|
|
bool IsInvalidateTextSelection() const
|
|
{
|
|
return bool( mnStates & AccessibleStates::TEXT_SELECTION_CHANGED );
|
|
}
|
|
|
|
bool IsInvalidateTextAttrs() const
|
|
{
|
|
return bool( mnStates & AccessibleStates::TEXT_ATTRIBUTE_CHANGED );
|
|
}
|
|
|
|
AccessibleStates GetStates() const
|
|
{
|
|
return mnStates;
|
|
}
|
|
|
|
AccessibleStates GetAllStates() const
|
|
{
|
|
return mnStates;
|
|
}
|
|
};
|
|
|
|
class SwAccessibleEventList_Impl
|
|
{
|
|
std::list<SwAccessibleEvent_Impl> maEvents;
|
|
bool mbFiring;
|
|
|
|
public:
|
|
SwAccessibleEventList_Impl()
|
|
: mbFiring( false )
|
|
{}
|
|
|
|
void SetFiring()
|
|
{
|
|
mbFiring = true;
|
|
}
|
|
bool IsFiring() const
|
|
{
|
|
return mbFiring;
|
|
}
|
|
|
|
void MoveMissingXAccToEnd();
|
|
|
|
size_t size() const { return maEvents.size(); }
|
|
std::list<SwAccessibleEvent_Impl>::iterator begin() { return maEvents.begin(); }
|
|
std::list<SwAccessibleEvent_Impl>::iterator end() { return maEvents.end(); }
|
|
std::list<SwAccessibleEvent_Impl>::iterator insert( const std::list<SwAccessibleEvent_Impl>::iterator& aIter,
|
|
const SwAccessibleEvent_Impl& rEvent )
|
|
{
|
|
return maEvents.insert( aIter, rEvent );
|
|
}
|
|
std::list<SwAccessibleEvent_Impl>::iterator erase( const std::list<SwAccessibleEvent_Impl>::iterator& aPos )
|
|
{
|
|
return maEvents.erase( aPos );
|
|
}
|
|
};
|
|
|
|
// see comment in SwAccessibleMap::InvalidatePosOrSize()
|
|
// last case "else if(pParent)" for why this surprising hack exists
|
|
void SwAccessibleEventList_Impl::MoveMissingXAccToEnd()
|
|
{
|
|
size_t nSize = size();
|
|
if (nSize < 2 )
|
|
{
|
|
return;
|
|
}
|
|
SwAccessibleEventList_Impl lstEvent;
|
|
for (auto li = begin(); li != end(); )
|
|
{
|
|
if (li->IsNoXaccParentFrame())
|
|
{
|
|
lstEvent.insert(lstEvent.end(), *li);
|
|
li = erase(li);
|
|
}
|
|
else
|
|
++li;
|
|
}
|
|
assert(size() + lstEvent.size() == nSize);
|
|
maEvents.insert(end(),lstEvent.begin(),lstEvent.end());
|
|
assert(size() == nSize);
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct SwAccessibleChildFunc
|
|
{
|
|
bool operator()( const SwAccessibleChild& r1,
|
|
const SwAccessibleChild& r2 ) const
|
|
{
|
|
const void *p1 = r1.GetSwFrame()
|
|
? static_cast < const void * >( r1.GetSwFrame())
|
|
: ( r1.GetDrawObject()
|
|
? static_cast < const void * >( r1.GetDrawObject() )
|
|
: static_cast < const void * >( r1.GetWindow() ) );
|
|
const void *p2 = r2.GetSwFrame()
|
|
? static_cast < const void * >( r2.GetSwFrame())
|
|
: ( r2.GetDrawObject()
|
|
? static_cast < const void * >( r2.GetDrawObject() )
|
|
: static_cast < const void * >( r2.GetWindow() ) );
|
|
return p1 < p2;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
class SwAccessibleEventMap_Impl
|
|
{
|
|
public:
|
|
typedef SwAccessibleChild key_type;
|
|
typedef std::list<SwAccessibleEvent_Impl>::iterator mapped_type;
|
|
typedef std::pair<const key_type,mapped_type> value_type;
|
|
typedef SwAccessibleChildFunc key_compare;
|
|
typedef std::map<key_type,mapped_type,key_compare>::iterator iterator;
|
|
typedef std::map<key_type,mapped_type,key_compare>::const_iterator const_iterator;
|
|
private:
|
|
std::map <key_type,mapped_type,key_compare> maMap;
|
|
public:
|
|
iterator end() { return maMap.end(); }
|
|
iterator find(const key_type& key) { return maMap.find(key); }
|
|
template<class... Args>
|
|
std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); }
|
|
iterator erase(const_iterator const & pos) { return maMap.erase(pos); }
|
|
};
|
|
|
|
namespace {
|
|
|
|
struct SwAccessibleParaSelection
|
|
{
|
|
TextFrameIndex nStartOfSelection;
|
|
TextFrameIndex nEndOfSelection;
|
|
|
|
SwAccessibleParaSelection(const TextFrameIndex nStartOfSelection_,
|
|
const TextFrameIndex nEndOfSelection_)
|
|
: nStartOfSelection(nStartOfSelection_)
|
|
, nEndOfSelection(nEndOfSelection_)
|
|
{}
|
|
};
|
|
|
|
struct SwXAccWeakRefComp
|
|
{
|
|
bool operator()( const unotools::WeakReference<SwAccessibleContext>& _rXAccWeakRef1,
|
|
const unotools::WeakReference<SwAccessibleContext>& _rXAccWeakRef2 ) const
|
|
{
|
|
return _rXAccWeakRef1.get() < _rXAccWeakRef2.get();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
class SwAccessibleSelectedParas_Impl
|
|
{
|
|
public:
|
|
typedef unotools::WeakReference < SwAccessibleContext > key_type;
|
|
typedef SwAccessibleParaSelection mapped_type;
|
|
typedef std::pair<const key_type,mapped_type> value_type;
|
|
typedef SwXAccWeakRefComp key_compare;
|
|
typedef std::map<key_type,mapped_type,key_compare>::iterator iterator;
|
|
typedef std::map<key_type,mapped_type,key_compare>::const_iterator const_iterator;
|
|
private:
|
|
std::map<key_type,mapped_type,key_compare> maMap;
|
|
public:
|
|
iterator begin() { return maMap.begin(); }
|
|
iterator end() { return maMap.end(); }
|
|
iterator find(const key_type& key) { return maMap.find(key); }
|
|
template<class... Args>
|
|
std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); }
|
|
iterator erase(const_iterator const & pos) { return maMap.erase(pos); }
|
|
};
|
|
|
|
// helper class that stores preview data
|
|
class SwAccPreviewData
|
|
{
|
|
typedef std::vector<tools::Rectangle> Rectangles;
|
|
Rectangles maPreviewRects;
|
|
Rectangles maLogicRects;
|
|
|
|
SwRect maVisArea;
|
|
Fraction maScale;
|
|
|
|
const SwPageFrame *mpSelPage;
|
|
|
|
/** adjust logic page rectangle to its visible part
|
|
|
|
@param _iorLogicPgSwRect
|
|
input/output parameter - reference to the logic page rectangle, which
|
|
has to be adjusted.
|
|
|
|
@param _rPreviewPgSwRect
|
|
input parameter - constant reference to the corresponding preview page
|
|
rectangle; needed to determine the visible part of the logic page rectangle.
|
|
|
|
@param _rPreviewWinSize
|
|
input parameter - constant reference to the preview window size in TWIP;
|
|
needed to determine the visible part of the logic page rectangle
|
|
*/
|
|
static void AdjustLogicPgRectToVisibleArea( SwRect& _iorLogicPgSwRect,
|
|
const SwRect& _rPreviewPgSwRect,
|
|
const Size& _rPreviewWinSize );
|
|
|
|
public:
|
|
SwAccPreviewData();
|
|
|
|
void Update( const SwAccessibleMap& rAccMap,
|
|
const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages,
|
|
const Fraction& _rScale,
|
|
const SwPageFrame* _pSelectedPageFrame,
|
|
const Size& _rPreviewWinSize );
|
|
|
|
void InvalidateSelection( const SwPageFrame* _pSelectedPageFrame );
|
|
|
|
const SwRect& GetVisArea() const { return maVisArea;}
|
|
|
|
/** Adjust the MapMode so that the preview page appears at the
|
|
* proper position. rPoint identifies the page for which the
|
|
* MapMode should be adjusted. If bFromPreview is true, rPoint is
|
|
* a preview coordinate; else it's a document coordinate. */
|
|
void AdjustMapMode( MapMode& rMapMode,
|
|
const Point& rPoint ) const;
|
|
|
|
const SwPageFrame *GetSelPage() const { return mpSelPage; }
|
|
|
|
void DisposePage(const SwPageFrame *pPageFrame );
|
|
};
|
|
|
|
SwAccPreviewData::SwAccPreviewData() :
|
|
mpSelPage( nullptr )
|
|
{
|
|
}
|
|
|
|
void SwAccPreviewData::Update( const SwAccessibleMap& rAccMap,
|
|
const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages,
|
|
const Fraction& _rScale,
|
|
const SwPageFrame* _pSelectedPageFrame,
|
|
const Size& _rPreviewWinSize )
|
|
{
|
|
// store preview scaling, maximal preview page size and selected page
|
|
maScale = _rScale;
|
|
mpSelPage = _pSelectedPageFrame;
|
|
|
|
// prepare loop on preview pages
|
|
maPreviewRects.clear();
|
|
maLogicRects.clear();
|
|
SwAccessibleChild aPage;
|
|
maVisArea.Clear();
|
|
|
|
// loop on preview pages to calculate <maPreviewRects>, <maLogicRects> and
|
|
// <maVisArea>
|
|
for ( auto & rpPreviewPage : _rPreviewPages )
|
|
{
|
|
aPage = rpPreviewPage->pPage;
|
|
|
|
// add preview page rectangle to <maPreviewRects>
|
|
tools::Rectangle aPreviewPgRect( rpPreviewPage->aPreviewWinPos, rpPreviewPage->aPageSize );
|
|
maPreviewRects.push_back( aPreviewPgRect );
|
|
|
|
// add logic page rectangle to <maLogicRects>
|
|
SwRect aLogicPgSwRect( aPage.GetBox( rAccMap ) );
|
|
tools::Rectangle aLogicPgRect( aLogicPgSwRect.SVRect() );
|
|
maLogicRects.push_back( aLogicPgRect );
|
|
// union visible area with visible part of logic page rectangle
|
|
if ( rpPreviewPage->bVisible )
|
|
{
|
|
if ( !rpPreviewPage->pPage->IsEmptyPage() )
|
|
{
|
|
AdjustLogicPgRectToVisibleArea( aLogicPgSwRect,
|
|
SwRect( aPreviewPgRect ),
|
|
_rPreviewWinSize );
|
|
}
|
|
if ( maVisArea.IsEmpty() )
|
|
maVisArea = aLogicPgSwRect;
|
|
else
|
|
maVisArea.Union( aLogicPgSwRect );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwAccPreviewData::InvalidateSelection( const SwPageFrame* _pSelectedPageFrame )
|
|
{
|
|
mpSelPage = _pSelectedPageFrame;
|
|
assert(mpSelPage);
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct ContainsPredicate
|
|
{
|
|
const Point& mrPoint;
|
|
explicit ContainsPredicate( const Point& rPoint ) : mrPoint(rPoint) {}
|
|
bool operator() ( const tools::Rectangle& rRect ) const
|
|
{
|
|
return rRect.Contains( mrPoint );
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
void SwAccPreviewData::AdjustMapMode( MapMode& rMapMode,
|
|
const Point& rPoint ) const
|
|
{
|
|
// adjust scale
|
|
rMapMode.SetScaleX( maScale );
|
|
rMapMode.SetScaleY( maScale );
|
|
|
|
// find proper rectangle
|
|
Rectangles::const_iterator aBegin = maLogicRects.begin();
|
|
Rectangles::const_iterator aEnd = maLogicRects.end();
|
|
Rectangles::const_iterator aFound = std::find_if( aBegin, aEnd,
|
|
ContainsPredicate( rPoint ) );
|
|
|
|
if( aFound != aEnd )
|
|
{
|
|
// found! set new origin
|
|
Point aPoint = (maPreviewRects.begin() + (aFound - aBegin))->TopLeft();
|
|
aPoint -= (maLogicRects.begin() + (aFound-aBegin))->TopLeft();
|
|
rMapMode.SetOrigin( aPoint );
|
|
}
|
|
// else: don't adjust MapMode
|
|
}
|
|
|
|
void SwAccPreviewData::DisposePage(const SwPageFrame *pPageFrame )
|
|
{
|
|
if( mpSelPage == pPageFrame )
|
|
mpSelPage = nullptr;
|
|
}
|
|
|
|
// adjust logic page rectangle to its visible part
|
|
void SwAccPreviewData::AdjustLogicPgRectToVisibleArea(
|
|
SwRect& _iorLogicPgSwRect,
|
|
const SwRect& _rPreviewPgSwRect,
|
|
const Size& _rPreviewWinSize )
|
|
{
|
|
// determine preview window rectangle
|
|
const SwRect aPreviewWinSwRect( Point( 0, 0 ), _rPreviewWinSize );
|
|
// calculate visible preview page rectangle
|
|
SwRect aVisPreviewPgSwRect( _rPreviewPgSwRect );
|
|
aVisPreviewPgSwRect.Intersection( aPreviewWinSwRect );
|
|
// adjust logic page rectangle
|
|
SwTwips nTmpDiff;
|
|
// left
|
|
nTmpDiff = aVisPreviewPgSwRect.Left() - _rPreviewPgSwRect.Left();
|
|
_iorLogicPgSwRect.AddLeft( nTmpDiff );
|
|
// top
|
|
nTmpDiff = aVisPreviewPgSwRect.Top() - _rPreviewPgSwRect.Top();
|
|
_iorLogicPgSwRect.AddTop( nTmpDiff );
|
|
// right
|
|
nTmpDiff = _rPreviewPgSwRect.Right() - aVisPreviewPgSwRect.Right();
|
|
_iorLogicPgSwRect.AddRight( - nTmpDiff );
|
|
// bottom
|
|
nTmpDiff = _rPreviewPgSwRect.Bottom() - aVisPreviewPgSwRect.Bottom();
|
|
_iorLogicPgSwRect.AddBottom( - nTmpDiff );
|
|
}
|
|
|
|
static bool AreInSameTable( const rtl::Reference< SwAccessibleContext >& rAcc,
|
|
const SwFrame *pFrame )
|
|
{
|
|
bool bRet = false;
|
|
|
|
if( pFrame && pFrame->IsCellFrame() && rAcc.is() )
|
|
{
|
|
// Is it in the same table? We check that
|
|
// by comparing the last table frame in the
|
|
// follow chain, because that's cheaper than
|
|
// searching the first one.
|
|
if( rAcc->GetFrame()->IsCellFrame() )
|
|
{
|
|
const SwTabFrame *pTabFrame1 = rAcc->GetFrame()->FindTabFrame();
|
|
if (pTabFrame1)
|
|
{
|
|
while (pTabFrame1->GetFollow())
|
|
pTabFrame1 = pTabFrame1->GetFollow();
|
|
}
|
|
|
|
const SwTabFrame *pTabFrame2 = pFrame->FindTabFrame();
|
|
if (pTabFrame2)
|
|
{
|
|
while (pTabFrame2->GetFollow())
|
|
pTabFrame2 = pTabFrame2->GetFollow();
|
|
}
|
|
|
|
bRet = (pTabFrame1 == pTabFrame2);
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void SwAccessibleMap::FireEvent( const SwAccessibleEvent_Impl& rEvent )
|
|
{
|
|
::rtl::Reference < SwAccessibleContext > xAccImpl( rEvent.GetContext() );
|
|
if (!xAccImpl.is() && rEvent.mpParentFrame != nullptr)
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( rEvent.mpParentFrame );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xContext( (*aIter).second.get() );
|
|
if (xContext.is() && (xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
|
|
|| xContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
|
|
{
|
|
xAccImpl = xContext.get();
|
|
}
|
|
}
|
|
}
|
|
if( SwAccessibleEvent_Impl::SHAPE_SELECTION == rEvent.GetType() )
|
|
{
|
|
DoInvalidateShapeSelection();
|
|
}
|
|
else if( xAccImpl.is() && xAccImpl->GetFrame() )
|
|
{
|
|
if ( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE &&
|
|
rEvent.IsInvalidateTextAttrs() )
|
|
{
|
|
xAccImpl->InvalidateAttr();
|
|
}
|
|
switch( rEvent.GetType() )
|
|
{
|
|
case SwAccessibleEvent_Impl::INVALID_CONTENT:
|
|
xAccImpl->InvalidateContent();
|
|
break;
|
|
case SwAccessibleEvent_Impl::POS_CHANGED:
|
|
xAccImpl->InvalidatePosOrSize( rEvent.GetOldBox() );
|
|
break;
|
|
case SwAccessibleEvent_Impl::CHILD_POS_CHANGED:
|
|
xAccImpl->InvalidateChildPosOrSize( rEvent.GetFrameOrObj(),
|
|
rEvent.GetOldBox() );
|
|
break;
|
|
case SwAccessibleEvent_Impl::DISPOSE:
|
|
assert(!"dispose event has been stored");
|
|
break;
|
|
case SwAccessibleEvent_Impl::INVALID_ATTR:
|
|
// nothing to do here - handled above
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() )
|
|
{
|
|
if( rEvent.IsUpdateCursorPos() )
|
|
xAccImpl->InvalidateCursorPos();
|
|
if( rEvent.IsInvalidateStates() )
|
|
xAccImpl->InvalidateStates( rEvent.GetStates() );
|
|
if( rEvent.IsInvalidateRelation() )
|
|
{
|
|
// both events CONTENT_FLOWS_FROM_RELATION_CHANGED and
|
|
// CONTENT_FLOWS_TO_RELATION_CHANGED are possible
|
|
if ( rEvent.GetAllStates() & AccessibleStates::RELATION_FROM )
|
|
{
|
|
xAccImpl->InvalidateRelation(
|
|
AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED );
|
|
}
|
|
if ( rEvent.GetAllStates() & AccessibleStates::RELATION_TO )
|
|
{
|
|
xAccImpl->InvalidateRelation(
|
|
AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED );
|
|
}
|
|
}
|
|
|
|
if ( rEvent.IsInvalidateTextSelection() )
|
|
{
|
|
xAccImpl->InvalidateTextSelection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::AppendEvent( const SwAccessibleEvent_Impl& rEvent )
|
|
{
|
|
osl::MutexGuard aGuard( maEventMutex );
|
|
|
|
if( !mpEvents )
|
|
mpEvents.reset(new SwAccessibleEventList_Impl);
|
|
if( !mpEventMap )
|
|
mpEventMap.reset(new SwAccessibleEventMap_Impl);
|
|
|
|
if( mpEvents->IsFiring() )
|
|
{
|
|
// While events are fired new ones are generated. They have to be fired
|
|
// now. This does not work for DISPOSE events!
|
|
OSL_ENSURE( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE,
|
|
"dispose event while firing events" );
|
|
FireEvent( rEvent );
|
|
}
|
|
else
|
|
{
|
|
|
|
SwAccessibleEventMap_Impl::iterator aIter =
|
|
mpEventMap->find( rEvent.GetFrameOrObj() );
|
|
if( aIter != mpEventMap->end() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent( *(*aIter).second );
|
|
assert( aEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE &&
|
|
"dispose events should not be stored" );
|
|
bool bAppendEvent = true;
|
|
switch( rEvent.GetType() )
|
|
{
|
|
case SwAccessibleEvent_Impl::CARET_OR_STATES:
|
|
// A CARET_OR_STATES event is added to any other
|
|
// event only. It is broadcasted after any other event, so the
|
|
// event should be put to the back.
|
|
OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
"invalid event combination" );
|
|
aEvent.SetStates( rEvent.GetAllStates() );
|
|
break;
|
|
case SwAccessibleEvent_Impl::INVALID_CONTENT:
|
|
// An INVALID_CONTENT event overwrites a CARET_OR_STATES
|
|
// event (but keeps its flags) and it is contained in a
|
|
// POS_CHANGED event.
|
|
// Therefore, the event's type has to be adapted and the event
|
|
// has to be put at the end.
|
|
//
|
|
// fdo#56031 An INVALID_CONTENT event overwrites a INVALID_ATTR
|
|
// event and overwrites its flags
|
|
OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
"invalid event combination" );
|
|
if( aEvent.GetType() == SwAccessibleEvent_Impl::CARET_OR_STATES )
|
|
aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT );
|
|
else if ( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR )
|
|
{
|
|
aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT );
|
|
aEvent.SetStates( rEvent.GetAllStates() );
|
|
}
|
|
|
|
break;
|
|
case SwAccessibleEvent_Impl::POS_CHANGED:
|
|
// A pos changed event overwrites CARET_STATES (keeping its
|
|
// flags) as well as INVALID_CONTENT. The old box position
|
|
// has to be stored however if the old event is not a
|
|
// POS_CHANGED itself.
|
|
OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
"invalid event combination" );
|
|
if( aEvent.GetType() != SwAccessibleEvent_Impl::POS_CHANGED )
|
|
aEvent.SetOldBox( rEvent.GetOldBox() );
|
|
aEvent.SetType( SwAccessibleEvent_Impl::POS_CHANGED );
|
|
break;
|
|
case SwAccessibleEvent_Impl::CHILD_POS_CHANGED:
|
|
// CHILD_POS_CHANGED events can only follow CHILD_POS_CHANGED
|
|
// events. The only action that needs to be done again is
|
|
// to put the old event to the back. The new one cannot be used,
|
|
// because we are interested in the old frame bounds.
|
|
OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
"invalid event combination" );
|
|
break;
|
|
case SwAccessibleEvent_Impl::SHAPE_SELECTION:
|
|
OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::SHAPE_SELECTION,
|
|
"invalid event combination" );
|
|
break;
|
|
case SwAccessibleEvent_Impl::DISPOSE:
|
|
// DISPOSE events overwrite all others. They are not stored
|
|
// but executed immediately to avoid broadcasting of
|
|
// nonfunctional objects. So what needs to be done here is to
|
|
// remove all events for the frame in question.
|
|
bAppendEvent = false;
|
|
break;
|
|
case SwAccessibleEvent_Impl::INVALID_ATTR:
|
|
// tdf#150708 if the old is CARET_OR_STATES then try updating it
|
|
// with the additional states
|
|
if (aEvent.GetType() == SwAccessibleEvent_Impl::CARET_OR_STATES)
|
|
aEvent.SetStates(rEvent.GetAllStates());
|
|
else
|
|
{
|
|
OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR,
|
|
"invalid event combination" );
|
|
}
|
|
break;
|
|
}
|
|
if( bAppendEvent )
|
|
{
|
|
mpEvents->erase( (*aIter).second );
|
|
(*aIter).second = mpEvents->insert( mpEvents->end(), aEvent );
|
|
}
|
|
else
|
|
{
|
|
mpEvents->erase( (*aIter).second );
|
|
mpEventMap->erase( aIter );
|
|
}
|
|
}
|
|
else if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() )
|
|
{
|
|
mpEventMap->emplace( rEvent.GetFrameOrObj(),
|
|
mpEvents->insert( mpEvents->end(), rEvent ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateCursorPosition(
|
|
const uno::Reference< XAccessible >& rAcc )
|
|
{
|
|
SwAccessibleContext *pAccImpl =
|
|
static_cast< SwAccessibleContext *>( rAcc.get() );
|
|
assert(pAccImpl);
|
|
assert(pAccImpl->GetFrame());
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES,
|
|
pAccImpl,
|
|
SwAccessibleChild(pAccImpl->GetFrame()),
|
|
AccessibleStates::CARET );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
// While firing events the current frame might have
|
|
// been disposed because it moved out of the visible area.
|
|
// Setting the cursor for such frames is useless and even
|
|
// causes asserts.
|
|
if( pAccImpl->GetFrame() )
|
|
pAccImpl->InvalidateCursorPos();
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateShapeSelection()
|
|
{
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::SHAPE_SELECTION );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
DoInvalidateShapeSelection();
|
|
}
|
|
}
|
|
|
|
//This method should implement the following functions:
|
|
//1.find the shape objects and set the selected state.
|
|
//2.find the Swframe objects and set the selected state.
|
|
//3.find the paragraph objects and set the selected state.
|
|
void SwAccessibleMap::InvalidateShapeInParaSelection()
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes;
|
|
SwAccessibleObjShape_Impl *pSelShape = nullptr;
|
|
size_t nShapes = 0;
|
|
|
|
const SwViewShell *pVSh = GetShell();
|
|
const SwFEShell *pFESh = dynamic_cast<const SwFEShell*>(pVSh);
|
|
SwPaM* pCursor = pFESh ? pFESh->GetCursor( false /* ??? */ ) : nullptr;
|
|
|
|
//const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0;
|
|
|
|
if( mpShapeMap )
|
|
pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape );
|
|
|
|
bool bIsSelAll =IsDocumentSelAll();
|
|
|
|
if( mpShapeMap )
|
|
{
|
|
//Checked for shapes.
|
|
SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin();
|
|
SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend();
|
|
|
|
if( bIsSelAll)
|
|
{
|
|
while( aIter != aEndIter )
|
|
{
|
|
rtl::Reference<::accessibility::AccessibleShape> xAcc( (*aIter).second );
|
|
if( xAcc.is() )
|
|
xAcc->SetState( AccessibleStateType::SELECTED );
|
|
|
|
++aIter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while( aIter != aEndIter )
|
|
{
|
|
const SwFrameFormat *pFrameFormat = (*aIter).first ? ::FindFrameFormat( (*aIter).first ) : nullptr;
|
|
if( !pFrameFormat )
|
|
{
|
|
++aIter;
|
|
continue;
|
|
}
|
|
const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
|
|
const SwNode *pAnchorNode = rAnchor.GetAnchorNode();
|
|
|
|
if(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE)
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if(xAcc.is())
|
|
xAcc->ResetState( AccessibleStateType::SELECTED );
|
|
|
|
++aIter;
|
|
continue;
|
|
}
|
|
|
|
if( !pAnchorNode )
|
|
{
|
|
++aIter;
|
|
continue;
|
|
}
|
|
if( pAnchorNode->GetTextNode() )
|
|
{
|
|
sal_Int32 nIndex = rAnchor.GetAnchorContentOffset();
|
|
bool bMarked = false;
|
|
if( pCursor != nullptr )
|
|
{
|
|
const SwTextNode* pNode = pAnchorNode->GetTextNode();
|
|
SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(pNode->getLayoutFrame(pVSh->GetLayout())));
|
|
SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex());
|
|
SwNodeOffset nLastNode;
|
|
if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
|
|
{
|
|
nLastNode = pMerged->pLastNode->GetIndex();
|
|
}
|
|
else
|
|
{
|
|
nLastNode = nFirstNode;
|
|
}
|
|
|
|
SwNodeOffset nHere = pNode->GetIndex();
|
|
|
|
for(SwPaM& rTmpCursor : pCursor->GetRingContainer())
|
|
{
|
|
// ignore, if no mark
|
|
if( rTmpCursor.HasMark() )
|
|
{
|
|
bMarked = true;
|
|
// check whether nHere is 'inside' pCursor
|
|
SwPosition* pStart = rTmpCursor.Start();
|
|
SwNodeOffset nStartIndex = pStart->GetNodeIndex();
|
|
SwPosition* pEnd = rTmpCursor.End();
|
|
SwNodeOffset nEndIndex = pEnd->GetNodeIndex();
|
|
if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex))
|
|
{
|
|
if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR )
|
|
{
|
|
if( ( ((nHere == nStartIndex) && (nIndex >= pStart->GetContentIndex())) || (nHere > nStartIndex) )
|
|
&&( ((nHere == nEndIndex) && (nIndex < pEnd->GetContentIndex())) || (nHere < nEndIndex) ) )
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if( xAcc.is() )
|
|
xAcc->SetState( AccessibleStateType::SELECTED );
|
|
}
|
|
else
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if( xAcc.is() )
|
|
xAcc->ResetState( AccessibleStateType::SELECTED );
|
|
}
|
|
}
|
|
else if( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA )
|
|
{
|
|
rtl::Reference<::accessibility::AccessibleShape> const xAcc((*aIter).second);
|
|
if (xAcc.is())
|
|
{
|
|
if (IsSelectFrameAnchoredAtPara(*rAnchor.GetContentAnchor(), *pStart, *pEnd))
|
|
{
|
|
xAcc->SetState( AccessibleStateType::SELECTED );
|
|
}
|
|
else
|
|
{
|
|
xAcc->ResetState( AccessibleStateType::SELECTED );
|
|
}
|
|
}
|
|
}
|
|
else if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
|
|
{
|
|
rtl::Reference<::accessibility::AccessibleShape> const xAcc((*aIter).second);
|
|
if (xAcc.is())
|
|
{
|
|
if (IsDestroyFrameAnchoredAtChar(*rAnchor.GetContentAnchor(), *pStart, *pEnd))
|
|
{
|
|
xAcc->SetState( AccessibleStateType::SELECTED );
|
|
}
|
|
else
|
|
{
|
|
xAcc->ResetState( AccessibleStateType::SELECTED );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( !bMarked )
|
|
{
|
|
SwAccessibleObjShape_Impl *pShape = pShapes.get();
|
|
size_t nNumShapes = nShapes;
|
|
while (nNumShapes > 0)
|
|
{
|
|
if( pShape < pSelShape && (pShape->first==(*aIter).first) )
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if(xAcc.is())
|
|
xAcc->ResetState( AccessibleStateType::SELECTED );
|
|
}
|
|
--nNumShapes;
|
|
++pShape;
|
|
}
|
|
}
|
|
}
|
|
|
|
++aIter;
|
|
}//while( aIter != aEndIter )
|
|
}//else
|
|
}
|
|
|
|
pShapes.reset();
|
|
|
|
//Checked for FlyFrame
|
|
if (mpFrameMap)
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin();
|
|
while( aIter != mpFrameMap->end() )
|
|
{
|
|
const SwFrame *pFrame = (*aIter).first;
|
|
if(pFrame->IsFlyFrame())
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc = (*aIter).second;
|
|
|
|
if(xAcc.is())
|
|
{
|
|
SwAccessibleFrameBase *pAccFrame = static_cast< SwAccessibleFrameBase * >(xAcc.get());
|
|
bool bFrameChanged = pAccFrame->SetSelectedState( true );
|
|
if (bFrameChanged)
|
|
{
|
|
const SwFlyFrame *pFlyFrame = static_cast< const SwFlyFrame * >( pFrame );
|
|
const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat();
|
|
if (pFrameFormat)
|
|
{
|
|
const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
|
|
if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR )
|
|
{
|
|
uno::Reference< XAccessible > xAccParent = pAccFrame->getAccessibleParent();
|
|
if (xAccParent.is())
|
|
{
|
|
uno::Reference< XAccessibleContext > xAccContext = xAccParent->getAccessibleContext();
|
|
if(xAccContext.is() && (xAccContext->getAccessibleRole() == AccessibleRole::PARAGRAPH ||
|
|
xAccContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
|
|
{
|
|
SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xAccContext.get());
|
|
if(pAccFrame->IsSelectedInDoc())
|
|
{
|
|
m_setParaAdd.insert(pAccPara);
|
|
}
|
|
else if(m_setParaAdd.count(pAccPara) == 0)
|
|
{
|
|
m_setParaRemove.insert(pAccPara);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++aIter;
|
|
}
|
|
}
|
|
|
|
typedef std::vector< SwAccessibleContext* > VEC_PARA;
|
|
VEC_PARA vecAdd;
|
|
VEC_PARA vecRemove;
|
|
//Checked for Paras.
|
|
bool bMarkChanged = false;
|
|
SwAccessibleContextMap_Impl mapTemp;
|
|
if( pCursor != nullptr )
|
|
{
|
|
for(SwPaM& rTmpCursor : pCursor->GetRingContainer())
|
|
{
|
|
if( rTmpCursor.HasMark() )
|
|
{
|
|
SwNodeIndex nStartIndex( rTmpCursor.Start()->GetNode() );
|
|
SwNodeIndex nEndIndex( rTmpCursor.End()->GetNode() );
|
|
for (; nStartIndex <= nEndIndex; ++nStartIndex)
|
|
{
|
|
SwFrame *pFrame = nullptr;
|
|
if(nStartIndex.GetNode().IsContentNode())
|
|
{
|
|
SwContentNode* pCNd = static_cast<SwContentNode*>(&(nStartIndex.GetNode()));
|
|
pFrame = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pCNd).First();
|
|
if (mapTemp.find(pFrame) != mapTemp.end())
|
|
{
|
|
continue; // sw_redlinehide: once is enough
|
|
}
|
|
}
|
|
else if( nStartIndex.GetNode().IsTableNode() )
|
|
{
|
|
SwTableNode * pTable = static_cast<SwTableNode *>(&(nStartIndex.GetNode()));
|
|
SwTableFormat* pFormat = pTable->GetTable().GetFrameFormat();
|
|
pFrame = SwIterator<SwFrame, SwTableFormat>(*pFormat).First();
|
|
}
|
|
|
|
if( pFrame && mpFrameMap)
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc = (*aIter).second;
|
|
bool isChanged = false;
|
|
if( xAcc.is() )
|
|
{
|
|
isChanged = xAcc->SetSelectedState( true );
|
|
}
|
|
if(!isChanged)
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aEraseIter = mpSelectedFrameMap->find( pFrame );
|
|
if(aEraseIter != mpSelectedFrameMap->end())
|
|
mpSelectedFrameMap->erase(aEraseIter);
|
|
}
|
|
else
|
|
{
|
|
bMarkChanged = true;
|
|
vecAdd.push_back(xAcc.get());
|
|
}
|
|
|
|
mapTemp.emplace( pFrame, xAcc );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( !mpSelectedFrameMap )
|
|
mpSelectedFrameMap.reset( new SwAccessibleContextMap_Impl );
|
|
if( !mpSelectedFrameMap->empty() )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpSelectedFrameMap->begin();
|
|
while( aIter != mpSelectedFrameMap->end() )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc = (*aIter).second;
|
|
if(xAcc.is())
|
|
xAcc->SetSelectedState( false );
|
|
++aIter;
|
|
vecRemove.push_back(xAcc.get());
|
|
}
|
|
bMarkChanged = true;
|
|
mpSelectedFrameMap->clear();
|
|
}
|
|
|
|
SwAccessibleContextMap_Impl::iterator aIter = mapTemp.begin();
|
|
while( aIter != mapTemp.end() )
|
|
{
|
|
mpSelectedFrameMap->emplace( (*aIter).first, (*aIter).second );
|
|
++aIter;
|
|
}
|
|
mapTemp.clear();
|
|
|
|
if( !(bMarkChanged && mpFrameMap))
|
|
return;
|
|
|
|
for (SwAccessibleContext* pAccPara : vecAdd)
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED;
|
|
if (pAccPara)
|
|
{
|
|
pAccPara->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
for (SwAccessibleContext* pAccPara : vecRemove)
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE;
|
|
if (pAccPara)
|
|
{
|
|
pAccPara->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
//Merge with DoInvalidateShapeFocus
|
|
void SwAccessibleMap::DoInvalidateShapeSelection(bool bInvalidateFocusMode /*=false*/)
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes;
|
|
SwAccessibleObjShape_Impl *pSelShape = nullptr;
|
|
size_t nShapes = 0;
|
|
|
|
const SwViewShell *pVSh = GetShell();
|
|
const SwFEShell *pFESh = dynamic_cast<const SwFEShell*>(pVSh);
|
|
const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0;
|
|
|
|
//when InvalidateFocus Call this function ,and the current selected shape count is not 1 ,
|
|
//return
|
|
if (bInvalidateFocusMode && nSelShapes != 1)
|
|
{
|
|
return;
|
|
}
|
|
if( mpShapeMap )
|
|
pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape );
|
|
|
|
if( !pShapes )
|
|
return;
|
|
|
|
std::vector<::rtl::Reference<::accessibility::AccessibleShape>> vecxShapeAdd;
|
|
std::vector<::rtl::Reference<::accessibility::AccessibleShape>> vecxShapeRemove;
|
|
|
|
vcl::Window *pWin = GetShell()->GetWin();
|
|
bool bFocused = pWin && pWin->HasFocus();
|
|
SwAccessibleObjShape_Impl *pShape = pShapes.get();
|
|
int nShapeCount = nShapes;
|
|
while( nShapeCount )
|
|
{
|
|
if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh))
|
|
{
|
|
if( pShape < pSelShape )
|
|
{
|
|
if(pShape->second->ResetState( AccessibleStateType::SELECTED ))
|
|
{
|
|
vecxShapeRemove.push_back(pShape->second);
|
|
}
|
|
pShape->second->ResetState( AccessibleStateType::FOCUSED );
|
|
}
|
|
}
|
|
--nShapeCount;
|
|
++pShape;
|
|
}
|
|
|
|
rtl::Reference<SwAccessibleContext> xDocView = GetDocumentView_(false);
|
|
assert(xDocView.is());
|
|
|
|
for (const auto& rpShape : vecxShapeRemove)
|
|
{
|
|
if (rpShape.is())
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE;
|
|
aEvent.NewValue <<= uno::Reference<XAccessible>(rpShape);
|
|
xDocView->FireAccessibleEvent(aEvent);
|
|
}
|
|
}
|
|
|
|
pShape = pShapes.get();
|
|
|
|
while( nShapes )
|
|
{
|
|
if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh))
|
|
{
|
|
if( pShape >= pSelShape )
|
|
{
|
|
//first fire focus event
|
|
if( bFocused && 1 == nSelShapes )
|
|
pShape->second->SetState( AccessibleStateType::FOCUSED );
|
|
else
|
|
pShape->second->ResetState( AccessibleStateType::FOCUSED );
|
|
|
|
if(pShape->second->SetState( AccessibleStateType::SELECTED ))
|
|
{
|
|
vecxShapeAdd.push_back(pShape->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
--nShapes;
|
|
++pShape;
|
|
}
|
|
|
|
const unsigned int SELECTION_WITH_NUM = 10;
|
|
if (vecxShapeAdd.size() > SELECTION_WITH_NUM )
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN;
|
|
xDocView->FireAccessibleEvent(aEvent);
|
|
}
|
|
else
|
|
{
|
|
for (const auto& rpShape : vecxShapeAdd)
|
|
{
|
|
if (rpShape.is())
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD;
|
|
aEvent.NewValue <<= uno::Reference<XAccessible>(rpShape);
|
|
xDocView->FireAccessibleEvent(aEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& rpShape : vecxShapeAdd)
|
|
{
|
|
::accessibility::AccessibleShape *pAccShape = rpShape.get();
|
|
if (pAccShape)
|
|
{
|
|
SdrObject *pObj = SdrObject::getSdrObjectFromXShape(pAccShape->GetXShape());
|
|
SwFrameFormat *pFrameFormat = pObj ? FindFrameFormat( pObj ) : nullptr;
|
|
if (pFrameFormat)
|
|
{
|
|
const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
|
|
if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR )
|
|
{
|
|
uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent();
|
|
if (xPara.is())
|
|
{
|
|
uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext();
|
|
if (xParaContext.is() && (xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
|
|
|| xParaContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
|
|
{
|
|
SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get());
|
|
if (pAccPara)
|
|
{
|
|
m_setParaAdd.insert(pAccPara);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const auto& rpShape : vecxShapeRemove)
|
|
{
|
|
::accessibility::AccessibleShape *pAccShape = rpShape.get();
|
|
if (pAccShape && !pAccShape->IsDisposed())
|
|
{
|
|
uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent();
|
|
uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext();
|
|
if (xParaContext.is() && (xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
|
|
|| xParaContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
|
|
{
|
|
SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get());
|
|
if (m_setParaAdd.count(pAccPara) == 0 )
|
|
{
|
|
m_setParaRemove.insert(pAccPara);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SwAccessibleMap::SwAccessibleMap( SwViewShell *pSh ) :
|
|
mpVSh( pSh ),
|
|
mbShapeSelected( false ),
|
|
maDocName(SwAccessibleContext::GetResource(STR_ACCESS_DOC_NAME))
|
|
{
|
|
pSh->GetLayout()->AddAccessibleShell();
|
|
}
|
|
|
|
SwAccessibleMap::~SwAccessibleMap()
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
if( mpFrameMap )
|
|
{
|
|
const SwRootFrame *pRootFrame = GetShell()->GetLayout();
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
if( !xAcc.is() )
|
|
assert(false); // let's hope this can't happen? the vcl::Window apparently owns the top-level
|
|
//xAcc = new SwAccessibleDocument(shared_from_this());
|
|
}
|
|
|
|
if(xAcc.is())
|
|
{
|
|
SwAccessibleDocumentBase *const pAcc =
|
|
static_cast<SwAccessibleDocumentBase *>(xAcc.get());
|
|
pAcc->Dispose( true );
|
|
}
|
|
#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
|
|
if( mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin();
|
|
while( aIter != mpFrameMap->end() )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xTmp = (*aIter).second;
|
|
if( xTmp.is() )
|
|
assert(xTmp->GetMap() == nullptr); // must be disposed
|
|
++aIter;
|
|
}
|
|
}
|
|
#endif
|
|
assert((!mpFrameMap || mpFrameMap->empty()) &&
|
|
"Frame map should be empty after disposing the root frame");
|
|
assert((!mpShapeMap || mpShapeMap->empty()) &&
|
|
"Object map should be empty after disposing the root frame");
|
|
mpFrameMap.reset();
|
|
mpShapeMap.reset();
|
|
mvShapes.clear();
|
|
mpSelectedParas.reset();
|
|
|
|
mpPreview.reset();
|
|
|
|
{
|
|
osl::MutexGuard aGuard( maEventMutex );
|
|
assert(!mpEvents);
|
|
assert(!mpEventMap);
|
|
mpEventMap.reset();
|
|
mpEvents.reset();
|
|
}
|
|
mpVSh->GetLayout()->RemoveAccessibleShell();
|
|
}
|
|
|
|
rtl::Reference<SwAccessibleContext> SwAccessibleMap::GetDocumentView_(
|
|
bool bPagePreview )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
bool bSetVisArea = false;
|
|
|
|
if( !mpFrameMap )
|
|
{
|
|
mpFrameMap.reset(new SwAccessibleContextMap_Impl);
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
mpFrameMap->mbLocked = false;
|
|
#endif
|
|
}
|
|
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
assert(!mpFrameMap->mbLocked);
|
|
mpFrameMap->mbLocked = true;
|
|
#endif
|
|
|
|
const SwRootFrame *pRootFrame = GetShell()->GetLayout();
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
if( xAcc.is() )
|
|
{
|
|
bSetVisArea = true; // Set VisArea when map mutex is not locked
|
|
}
|
|
else
|
|
{
|
|
if( bPagePreview )
|
|
xAcc = new SwAccessiblePreview(shared_from_this());
|
|
else
|
|
xAcc = new SwAccessibleDocument(shared_from_this());
|
|
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
(*aIter).second = xAcc.get();
|
|
}
|
|
else
|
|
{
|
|
mpFrameMap->emplace( pRootFrame, xAcc );
|
|
}
|
|
}
|
|
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
mpFrameMap->mbLocked = false;
|
|
#endif
|
|
|
|
if( bSetVisArea )
|
|
{
|
|
SwAccessibleDocumentBase *pAcc =
|
|
static_cast< SwAccessibleDocumentBase * >( xAcc.get() );
|
|
pAcc->SetVisArea();
|
|
}
|
|
|
|
return xAcc;
|
|
}
|
|
|
|
uno::Reference< XAccessible > SwAccessibleMap::GetDocumentView( )
|
|
{
|
|
return GetDocumentView_( false );
|
|
}
|
|
|
|
uno::Reference<XAccessible> SwAccessibleMap::GetDocumentPreview(
|
|
const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages,
|
|
const Fraction& _rScale,
|
|
const SwPageFrame* _pSelectedPageFrame,
|
|
const Size& _rPreviewWinSize )
|
|
{
|
|
// create & update preview data object
|
|
if( mpPreview == nullptr )
|
|
mpPreview.reset( new SwAccPreviewData() );
|
|
mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize );
|
|
|
|
uno::Reference<XAccessible> xAcc = GetDocumentView_( true );
|
|
return xAcc;
|
|
}
|
|
|
|
uno::Reference< XAccessible> SwAccessibleMap::GetContext( const SwFrame *pFrame,
|
|
bool bCreate )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
rtl::Reference < SwAccessibleContext > xOldCursorAcc;
|
|
bool bOldShapeSelected = false;
|
|
|
|
if( !mpFrameMap && bCreate )
|
|
mpFrameMap.reset(new SwAccessibleContextMap_Impl);
|
|
if( mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
|
|
if( !xAcc.is() && bCreate )
|
|
{
|
|
rtl::Reference<SwAccessibleContext> pAcc;
|
|
switch( pFrame->GetType() )
|
|
{
|
|
case SwFrameType::Txt:
|
|
pAcc = new SwAccessibleParagraph(shared_from_this(),
|
|
static_cast< const SwTextFrame& >( *pFrame ) );
|
|
break;
|
|
case SwFrameType::Header:
|
|
pAcc = new SwAccessibleHeaderFooter(shared_from_this(),
|
|
static_cast< const SwHeaderFrame *>( pFrame ) );
|
|
break;
|
|
case SwFrameType::Footer:
|
|
pAcc = new SwAccessibleHeaderFooter(shared_from_this(),
|
|
static_cast< const SwFooterFrame *>( pFrame ) );
|
|
break;
|
|
case SwFrameType::Ftn:
|
|
{
|
|
const SwFootnoteFrame *pFootnoteFrame =
|
|
static_cast < const SwFootnoteFrame * >( pFrame );
|
|
bool bIsEndnote =
|
|
SwAccessibleFootnote::IsEndnote( pFootnoteFrame );
|
|
pAcc = new SwAccessibleFootnote(shared_from_this(), bIsEndnote,
|
|
/*(bIsEndnote ? mnEndnote++ : mnFootnote++),*/
|
|
pFootnoteFrame );
|
|
}
|
|
break;
|
|
case SwFrameType::Fly:
|
|
{
|
|
const SwFlyFrame *pFlyFrame =
|
|
static_cast < const SwFlyFrame * >( pFrame );
|
|
switch( SwAccessibleFrameBase::GetNodeType( pFlyFrame ) )
|
|
{
|
|
case SwNodeType::Grf:
|
|
pAcc = new SwAccessibleGraphic(shared_from_this(), pFlyFrame );
|
|
break;
|
|
case SwNodeType::Ole:
|
|
pAcc = new SwAccessibleEmbeddedObject(shared_from_this(), pFlyFrame );
|
|
break;
|
|
default:
|
|
pAcc = new SwAccessibleTextFrame(shared_from_this(), *pFlyFrame );
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SwFrameType::Cell:
|
|
pAcc = new SwAccessibleCell(shared_from_this(),
|
|
static_cast< const SwCellFrame *>( pFrame ) );
|
|
break;
|
|
case SwFrameType::Tab:
|
|
pAcc = new SwAccessibleTable(shared_from_this(),
|
|
static_cast< const SwTabFrame *>( pFrame ) );
|
|
break;
|
|
case SwFrameType::Page:
|
|
OSL_ENSURE( GetShell()->IsPreview(),
|
|
"accessible page frames only in PagePreview" );
|
|
pAcc = new SwAccessiblePage(shared_from_this(), pFrame);
|
|
break;
|
|
default: break;
|
|
}
|
|
xAcc = pAcc;
|
|
assert(xAcc.is());
|
|
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
(*aIter).second = xAcc.get();
|
|
}
|
|
else
|
|
{
|
|
mpFrameMap->emplace( pFrame, xAcc );
|
|
}
|
|
|
|
if( pAcc->HasCursor() &&
|
|
!AreInSameTable( mxCursorContext, pFrame ) )
|
|
{
|
|
// If the new context has the focus, and if we know
|
|
// another context that had the focus, then the focus
|
|
// just moves from the old context to the new one. We
|
|
// then have to send a focus event and a caret event for
|
|
// the old context. We have to do that now,
|
|
// because after we have left this method, anyone might
|
|
// call getStates for the new context and will get a
|
|
// focused state then. Sending the focus changes event
|
|
// after that seems to be strange. However, we cannot
|
|
// send a focus event for the new context now, because
|
|
// no one except us knows it. In any case, we remember
|
|
// the new context as the one that has the focus
|
|
// currently.
|
|
|
|
xOldCursorAcc = mxCursorContext;
|
|
mxCursorContext = xAcc.get();
|
|
|
|
bOldShapeSelected = mbShapeSelected;
|
|
mbShapeSelected = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invalidate focus for old object when map is not locked
|
|
if( xOldCursorAcc.is() )
|
|
InvalidateCursorPosition( xOldCursorAcc );
|
|
if( bOldShapeSelected )
|
|
InvalidateShapeSelection();
|
|
|
|
return xAcc;
|
|
}
|
|
|
|
::rtl::Reference < SwAccessibleContext > SwAccessibleMap::GetContextImpl(
|
|
const SwFrame *pFrame,
|
|
bool bCreate )
|
|
{
|
|
uno::Reference < XAccessible > xAcc( GetContext( pFrame, bCreate ) );
|
|
|
|
::rtl::Reference < SwAccessibleContext > xAccImpl(
|
|
static_cast< SwAccessibleContext * >( xAcc.get() ) );
|
|
|
|
return xAccImpl;
|
|
}
|
|
|
|
uno::Reference< XAccessible> SwAccessibleMap::GetContext(
|
|
const SdrObject *pObj,
|
|
SwAccessibleContext *pParentImpl,
|
|
bool bCreate )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc;
|
|
uno::Reference < XAccessible > xOldCursorAcc;
|
|
|
|
if( !mpShapeMap && bCreate )
|
|
mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this ));
|
|
if( mpShapeMap )
|
|
{
|
|
SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj );
|
|
if( aIter != mpShapeMap->end() )
|
|
xAcc = (*aIter).second;
|
|
|
|
if( !xAcc.is() && bCreate )
|
|
{
|
|
rtl::Reference< ::accessibility::AccessibleShape> pAcc;
|
|
uno::Reference < drawing::XShape > xShape(
|
|
const_cast< SdrObject * >( pObj )->getUnoShape(),
|
|
uno::UNO_QUERY );
|
|
if( xShape.is() )
|
|
{
|
|
::accessibility::ShapeTypeHandler& rShapeTypeHandler =
|
|
::accessibility::ShapeTypeHandler::Instance();
|
|
|
|
::accessibility::AccessibleShapeInfo aShapeInfo(
|
|
xShape, uno::Reference<XAccessible>(pParentImpl), this );
|
|
|
|
pAcc = rShapeTypeHandler.CreateAccessibleObject(
|
|
aShapeInfo, mpShapeMap->GetInfo() );
|
|
}
|
|
xAcc = pAcc.get();
|
|
assert(xAcc.is());
|
|
pAcc->Init();
|
|
if( aIter != mpShapeMap->end() )
|
|
{
|
|
(*aIter).second = xAcc.get();
|
|
}
|
|
else
|
|
{
|
|
mpShapeMap->emplace( pObj, xAcc );
|
|
}
|
|
// TODO: focus!!!
|
|
AddGroupContext(pObj, xAcc);
|
|
}
|
|
}
|
|
|
|
// Invalidate focus for old object when map is not locked
|
|
if( xOldCursorAcc.is() )
|
|
InvalidateCursorPosition( xOldCursorAcc );
|
|
|
|
return xAcc;
|
|
}
|
|
|
|
bool SwAccessibleMap::IsInSameLevel(const SdrObject* pObj, const SwFEShell* pFESh)
|
|
{
|
|
if (pFESh)
|
|
return pFESh->IsObjSameLevelWithMarked(pObj);
|
|
return false;
|
|
}
|
|
|
|
void SwAccessibleMap::AddShapeContext(const SdrObject *pObj, rtl::Reference < ::accessibility::AccessibleShape > const & xAccShape)
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if( mpShapeMap )
|
|
{
|
|
mpShapeMap->emplace( pObj, xAccShape );
|
|
}
|
|
}
|
|
|
|
//Added by yanjun for sym2_6407
|
|
void SwAccessibleMap::RemoveGroupContext(const SdrObject *pParentObj)
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
// TODO: Why are sub-shapes of group shapes even added to our map?
|
|
// Doesn't the AccessibleShape of the top-level shape create them
|
|
// on demand anyway? Why does SwAccessibleMap need to know them?
|
|
// We cannot rely on getAccessibleChild here to remove the sub-shapes
|
|
// from mpShapes because the top-level shape may not only be disposed here
|
|
// but also by visibility checks in svx, then it doesn't return children.
|
|
if (mpShapeMap && pParentObj && pParentObj->IsGroupObject())
|
|
{
|
|
if (SdrObjList *const pChildren = pParentObj->GetSubList())
|
|
for (const rtl::Reference<SdrObject>& pChild : *pChildren)
|
|
{
|
|
assert(pChild);
|
|
RemoveContext(pChild.get());
|
|
}
|
|
}
|
|
}
|
|
//End
|
|
|
|
void SwAccessibleMap::AddGroupContext(const SdrObject *pParentObj, uno::Reference < XAccessible > const & xAccParent)
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if( !mpShapeMap )
|
|
return;
|
|
|
|
//here get all the sub list.
|
|
if (!pParentObj->IsGroupObject())
|
|
return;
|
|
|
|
if (!xAccParent.is())
|
|
return;
|
|
|
|
uno::Reference < XAccessibleContext > xContext = xAccParent->getAccessibleContext();
|
|
if (!xContext.is())
|
|
return;
|
|
|
|
sal_Int64 nChildren = xContext->getAccessibleChildCount();
|
|
for(sal_Int64 i = 0; i<nChildren; i++)
|
|
{
|
|
uno::Reference < XAccessible > xChild = xContext->getAccessibleChild(i);
|
|
if (xChild.is())
|
|
{
|
|
uno::Reference < XAccessibleContext > xChildContext = xChild->getAccessibleContext();
|
|
if (xChildContext.is())
|
|
{
|
|
short nRole = xChildContext->getAccessibleRole();
|
|
if (nRole == AccessibleRole::SHAPE)
|
|
{
|
|
::accessibility::AccessibleShape* pAccShape = static_cast < ::accessibility::AccessibleShape* >( xChild.get());
|
|
uno::Reference < drawing::XShape > xShape = pAccShape->GetXShape();
|
|
if (xShape.is())
|
|
{
|
|
SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape);
|
|
AddShapeContext(pObj, pAccShape);
|
|
AddGroupContext(pObj,xChild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
::rtl::Reference < ::accessibility::AccessibleShape > SwAccessibleMap::GetContextImpl(
|
|
const SdrObject *pObj,
|
|
SwAccessibleContext *pParentImpl,
|
|
bool bCreate )
|
|
{
|
|
uno::Reference < XAccessible > xAcc( GetContext( pObj, pParentImpl, bCreate ) );
|
|
|
|
::rtl::Reference < ::accessibility::AccessibleShape > xAccImpl(
|
|
static_cast< ::accessibility::AccessibleShape* >( xAcc.get() ) );
|
|
|
|
return xAccImpl;
|
|
}
|
|
|
|
void SwAccessibleMap::RemoveContext( const SwFrame *pFrame )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if( !mpFrameMap )
|
|
return;
|
|
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( pFrame );
|
|
if( aIter == mpFrameMap->end() )
|
|
return;
|
|
|
|
mpFrameMap->erase( aIter );
|
|
|
|
if (mpSelectedFrameMap)
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aSelectedIter = mpSelectedFrameMap->find(pFrame);
|
|
if (aSelectedIter != mpSelectedFrameMap->end())
|
|
mpSelectedFrameMap->erase(aSelectedIter);
|
|
}
|
|
|
|
// Remove reference to old caret object. Though mxCursorContext
|
|
// is a weak reference and cleared automatically, clearing it
|
|
// directly makes sure to not keep a non-functional object.
|
|
rtl::Reference < SwAccessibleContext > xOldAcc( mxCursorContext );
|
|
if( xOldAcc.is() )
|
|
{
|
|
SwAccessibleContext *pOldAccImpl = xOldAcc.get();
|
|
OSL_ENSURE( pOldAccImpl->GetFrame(), "old caret context is disposed" );
|
|
if( pOldAccImpl->GetFrame() == pFrame )
|
|
{
|
|
xOldAcc.clear(); // get an empty ref
|
|
mxCursorContext = xOldAcc.get();
|
|
}
|
|
}
|
|
|
|
if( mpFrameMap->empty() )
|
|
{
|
|
mpFrameMap.reset();
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::RemoveContext( const SdrObject *pObj )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if( !mpShapeMap )
|
|
return;
|
|
|
|
SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj );
|
|
if( aIter == mpShapeMap->end() )
|
|
return;
|
|
|
|
rtl::Reference < ::accessibility::AccessibleShape > xTempHold( (*aIter).second );
|
|
mpShapeMap->erase( aIter );
|
|
RemoveGroupContext(pObj);
|
|
// The shape selection flag is not cleared, but one might do
|
|
// so but has to make sure that the removed context is the one
|
|
// that is selected.
|
|
|
|
if( mpShapeMap && mpShapeMap->empty() )
|
|
{
|
|
mpShapeMap.reset();
|
|
}
|
|
}
|
|
|
|
bool SwAccessibleMap::Contains(const SwFrame *pFrame) const
|
|
{
|
|
return (pFrame && mpFrameMap && mpFrameMap->find(pFrame) != mpFrameMap->end());
|
|
}
|
|
|
|
void SwAccessibleMap::A11yDispose( const SwFrame *pFrame,
|
|
const SdrObject *pObj,
|
|
vcl::Window* pWindow,
|
|
bool bRecursive,
|
|
bool bCanSkipInvisible )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow );
|
|
|
|
// Indeed, the following assert checks the frame's accessible flag,
|
|
// because that's the one that is evaluated in the layout. The frame
|
|
// might not be accessible anyway. That's the case for cell frames that
|
|
// contain further cells.
|
|
OSL_ENSURE( !aFrameOrObj.GetSwFrame() || aFrameOrObj.GetSwFrame()->IsAccessibleFrame(),
|
|
"non accessible frame should be disposed" );
|
|
|
|
if (!(aFrameOrObj.IsAccessible(GetShell()->IsPreview())
|
|
// fdo#87199 dispose the darn thing if it ever was accessible
|
|
|| Contains(pFrame)))
|
|
return;
|
|
|
|
::rtl::Reference< SwAccessibleContext > xAccImpl;
|
|
::rtl::Reference< SwAccessibleContext > xParentAccImpl;
|
|
::rtl::Reference< ::accessibility::AccessibleShape > xShapeAccImpl;
|
|
// get accessible context for frame
|
|
{
|
|
// First of all look for an accessible context for a frame
|
|
if( aFrameOrObj.GetSwFrame() && mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAccImpl = (*aIter).second;
|
|
}
|
|
if( !xAccImpl.is() && mpFrameMap )
|
|
{
|
|
// If there is none, look if the parent is accessible.
|
|
const SwFrame *pParent =
|
|
SwAccessibleFrame::GetParent( aFrameOrObj,
|
|
GetShell()->IsPreview());
|
|
|
|
if( pParent )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( pParent );
|
|
if( aIter != mpFrameMap->end() )
|
|
xParentAccImpl = (*aIter).second;
|
|
}
|
|
}
|
|
if( !xParentAccImpl.is() && !aFrameOrObj.GetSwFrame() && mpShapeMap )
|
|
{
|
|
SwAccessibleShapeMap_Impl::iterator aIter =
|
|
mpShapeMap->find( aFrameOrObj.GetDrawObject() );
|
|
if( aIter != mpShapeMap->end() )
|
|
{
|
|
xShapeAccImpl = aIter->second;
|
|
}
|
|
}
|
|
if( pObj && GetShell()->ActionPend() &&
|
|
(xParentAccImpl.is() || xShapeAccImpl.is()) )
|
|
{
|
|
// Keep a reference to the XShape to avoid that it
|
|
// is deleted with a SwFrameFormat::SwClientNotify.
|
|
uno::Reference < drawing::XShape > xShape(
|
|
const_cast< SdrObject * >( pObj )->getUnoShape(),
|
|
uno::UNO_QUERY );
|
|
if( xShape.is() )
|
|
{
|
|
mvShapes.push_back( xShape );
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove events stored for the frame
|
|
{
|
|
osl::MutexGuard aGuard( maEventMutex );
|
|
if( mpEvents )
|
|
{
|
|
SwAccessibleEventMap_Impl::iterator aIter =
|
|
mpEventMap->find( aFrameOrObj );
|
|
if( aIter != mpEventMap->end() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::DISPOSE, aFrameOrObj );
|
|
AppendEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the frame is accessible and there is a context for it, dispose
|
|
// the frame. If the frame is no context for it but disposing should
|
|
// take place recursive, the frame's children have to be disposed
|
|
// anyway, so we have to create the context then.
|
|
if( xAccImpl.is() )
|
|
{
|
|
xAccImpl->Dispose( bRecursive );
|
|
}
|
|
else if( xParentAccImpl.is() )
|
|
{
|
|
// If the frame is a cell frame, the table must be notified.
|
|
// If we are in an action, a table model change event will
|
|
// be broadcasted at the end of the action to give the table
|
|
// a chance to generate a single table change event.
|
|
|
|
xParentAccImpl->DisposeChild( aFrameOrObj, bRecursive, bCanSkipInvisible );
|
|
}
|
|
else if( xShapeAccImpl.is() )
|
|
{
|
|
RemoveContext( aFrameOrObj.GetDrawObject() );
|
|
xShapeAccImpl->dispose();
|
|
}
|
|
|
|
if( mpPreview && pFrame && pFrame->IsPageFrame() )
|
|
mpPreview->DisposePage( static_cast< const SwPageFrame *>( pFrame ) );
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidatePosOrSize( const SwFrame *pFrame,
|
|
const SdrObject *pObj,
|
|
vcl::Window* pWindow,
|
|
const SwRect& rOldBox )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow );
|
|
if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
return;
|
|
|
|
::rtl::Reference< SwAccessibleContext > xAccImpl;
|
|
::rtl::Reference< SwAccessibleContext > xParentAccImpl;
|
|
const SwFrame *pParent =nullptr;
|
|
if( mpFrameMap )
|
|
{
|
|
if( aFrameOrObj.GetSwFrame() )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
// If there is an accessible object already it is
|
|
// notified directly.
|
|
xAccImpl = (*aIter).second;
|
|
}
|
|
}
|
|
if( !xAccImpl.is() )
|
|
{
|
|
// Otherwise we look if the parent is accessible.
|
|
// If not, there is nothing to do.
|
|
pParent =
|
|
SwAccessibleFrame::GetParent( aFrameOrObj,
|
|
GetShell()->IsPreview());
|
|
|
|
if( pParent )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( pParent );
|
|
if( aIter != mpFrameMap->end() )
|
|
xParentAccImpl = (*aIter).second;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( xAccImpl.is() )
|
|
{
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::POS_CHANGED, xAccImpl.get(),
|
|
std::move(aFrameOrObj), rOldBox );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
if (xAccImpl->GetFrame()) // not if disposed by FireEvents()
|
|
{
|
|
xAccImpl->InvalidatePosOrSize(rOldBox);
|
|
}
|
|
}
|
|
}
|
|
else if( xParentAccImpl.is() )
|
|
{
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
assert(pParent);
|
|
// tdf#99722 faster not to buffer events that won't be sent
|
|
if (!SwAccessibleChild(pParent).IsVisibleChildrenOnly()
|
|
|| xParentAccImpl->IsShowing(rOldBox)
|
|
|| xParentAccImpl->IsShowing(*this, aFrameOrObj))
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
xParentAccImpl.get(), std::move(aFrameOrObj), rOldBox );
|
|
AppendEvent( aEvent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
xParentAccImpl->InvalidateChildPosOrSize( aFrameOrObj,
|
|
rOldBox );
|
|
}
|
|
}
|
|
else if(pParent)
|
|
{
|
|
/*
|
|
For child graphic and its parent paragraph,if split 2 graphic to 2 paragraph,
|
|
will delete one graphic swfrm and new create 1 graphic swfrm ,
|
|
then the new paragraph and the new graphic SwFrame will add .
|
|
but when add graphic SwFrame ,the accessible of the new Paragraph is not created yet.
|
|
so the new graphic accessible 'parent is NULL,
|
|
so run here: save the parent's SwFrame not the accessible object parent,
|
|
*/
|
|
bool bIsValidFrame = false;
|
|
bool bIsTextParent = false;
|
|
if (aFrameOrObj.GetSwFrame())
|
|
{
|
|
if (SwFrameType::Fly == pFrame->GetType())
|
|
{
|
|
bIsValidFrame =true;
|
|
}
|
|
}
|
|
else if(pObj)
|
|
{
|
|
if (SwFrameType::Txt == pParent->GetType())
|
|
{
|
|
bIsTextParent =true;
|
|
}
|
|
}
|
|
if( bIsValidFrame || bIsTextParent )
|
|
{
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::CHILD_POS_CHANGED,
|
|
pParent, std::move(aFrameOrObj), rOldBox );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE(false,"");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateContent( const SwFrame *pFrame )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
SwAccessibleChild aFrameOrObj( pFrame );
|
|
if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
return;
|
|
|
|
if (!mpFrameMap)
|
|
return;
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
|
|
if( !xAcc.is() )
|
|
return;
|
|
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::INVALID_CONTENT, xAcc.get(),
|
|
std::move(aFrameOrObj) );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
xAcc->InvalidateContent();
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateAttr( const SwTextFrame& rTextFrame )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
SwAccessibleChild aFrameOrObj( &rTextFrame );
|
|
if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
return;
|
|
|
|
if (!mpFrameMap)
|
|
return;
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
|
|
if( !xAcc.is() )
|
|
return;
|
|
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::INVALID_ATTR,
|
|
xAcc.get(), std::move(aFrameOrObj) );
|
|
aEvent.SetStates( AccessibleStates::TEXT_ATTRIBUTE_CHANGED );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
xAcc->InvalidateAttr();
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateCursorPosition( const SwFrame *pFrame )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
SwAccessibleChild aFrameOrObj( pFrame );
|
|
bool bShapeSelected = false;
|
|
const SwViewShell *pVSh = GetShell();
|
|
if( auto pCSh = dynamic_cast<const SwCursorShell*>(pVSh) )
|
|
{
|
|
if( pCSh->IsTableMode() )
|
|
{
|
|
while( aFrameOrObj.GetSwFrame() && !aFrameOrObj.GetSwFrame()->IsCellFrame() )
|
|
aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper();
|
|
}
|
|
else if( auto pFESh = dynamic_cast<const SwFEShell*>(pVSh) )
|
|
{
|
|
const SwFrame *pFlyFrame = pFESh->GetSelectedFlyFrame();
|
|
if( pFlyFrame )
|
|
{
|
|
OSL_ENSURE( !pFrame || pFrame->FindFlyFrame() == pFlyFrame,
|
|
"cursor is not contained in fly frame" );
|
|
aFrameOrObj = pFlyFrame;
|
|
}
|
|
else if( pFESh->IsObjSelected() > 0 )
|
|
{
|
|
bShapeSelected = true;
|
|
aFrameOrObj = static_cast<const SwFrame *>( nullptr );
|
|
}
|
|
}
|
|
}
|
|
|
|
OSL_ENSURE( bShapeSelected || aFrameOrObj.IsAccessible(GetShell()->IsPreview()),
|
|
"frame is not accessible" );
|
|
|
|
rtl::Reference < SwAccessibleContext > xOldAcc;
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
bool bOldShapeSelected = false;
|
|
|
|
{
|
|
xOldAcc = mxCursorContext;
|
|
mxCursorContext = xAcc.get(); // clear reference
|
|
|
|
bOldShapeSelected = mbShapeSelected;
|
|
mbShapeSelected = bShapeSelected;
|
|
|
|
if( aFrameOrObj.GetSwFrame() && mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
else
|
|
{
|
|
SwRect rcEmpty;
|
|
const SwTabFrame* pTabFrame = aFrameOrObj.GetSwFrame()->FindTabFrame();
|
|
if (pTabFrame)
|
|
{
|
|
InvalidatePosOrSize(pTabFrame, nullptr, nullptr, rcEmpty);
|
|
}
|
|
else
|
|
{
|
|
InvalidatePosOrSize(aFrameOrObj.GetSwFrame(), nullptr, nullptr, rcEmpty);
|
|
}
|
|
|
|
aIter = mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
xAcc = (*aIter).second;
|
|
}
|
|
}
|
|
|
|
// For cells, some extra thoughts are necessary,
|
|
// because invalidating the cursor for one cell
|
|
// invalidates the cursor for all cells of the same
|
|
// table. For this reason, we don't want to
|
|
// invalidate the cursor for the old cursor object
|
|
// and the new one if they are within the same table,
|
|
// because this would result in doing the work twice.
|
|
// Moreover, we have to make sure to invalidate the
|
|
// cursor even if the current cell has no accessible object.
|
|
// If the old cursor objects exists and is in the same
|
|
// table, it's the best choice, because using it avoids
|
|
// an unnecessary cursor invalidation cycle when creating
|
|
// a new object for the current cell.
|
|
if( aFrameOrObj.GetSwFrame()->IsCellFrame() )
|
|
{
|
|
if( xOldAcc.is() &&
|
|
AreInSameTable( xOldAcc, aFrameOrObj.GetSwFrame() ) )
|
|
{
|
|
if( xAcc.is() )
|
|
xOldAcc = xAcc; // avoid extra invalidation
|
|
else
|
|
xAcc = xOldAcc; // make sure at least one
|
|
}
|
|
if( !xAcc.is() )
|
|
xAcc = GetContextImpl( aFrameOrObj.GetSwFrame() );
|
|
}
|
|
}
|
|
else if (bShapeSelected)
|
|
{
|
|
const SwFEShell *pFESh = static_cast< const SwFEShell * >( pVSh );
|
|
const SdrMarkList *pMarkList = pFESh->GetMarkList();
|
|
if (pMarkList != nullptr && pMarkList->GetMarkCount() == 1)
|
|
{
|
|
SdrObject *pObj = pMarkList->GetMark( 0 )->GetMarkedSdrObj();
|
|
::rtl::Reference < ::accessibility::AccessibleShape > pAccShapeImpl = GetContextImpl(pObj,nullptr,false);
|
|
if (!pAccShapeImpl.is())
|
|
{
|
|
while (pObj && pObj->getParentSdrObjectFromSdrObject())
|
|
{
|
|
pObj = pObj->getParentSdrObjectFromSdrObject();
|
|
}
|
|
if (pObj != nullptr)
|
|
{
|
|
const SwFrame *pParent = SwAccessibleFrame::GetParent( SwAccessibleChild(pObj), GetShell()->IsPreview() );
|
|
if( pParent )
|
|
{
|
|
::rtl::Reference< SwAccessibleContext > xParentAccImpl = GetContextImpl(pParent,false);
|
|
if (!xParentAccImpl.is())
|
|
{
|
|
const SwTabFrame* pTabFrame = pParent->FindTabFrame();
|
|
if (pTabFrame)
|
|
{
|
|
//The Table should not add in acc.because the "pParent" is not add to acc .
|
|
uno::Reference< XAccessible> xAccParentTab = GetContext(pTabFrame);//Should Create.
|
|
|
|
const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pTabFrame), GetShell()->IsPreview() );
|
|
if (pParentRoot)
|
|
{
|
|
::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false);
|
|
if(xParentAccImplRoot.is())
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::CHILD;
|
|
aEvent.NewValue <<= xAccParentTab;
|
|
aEvent.IndexHint = -1;
|
|
xParentAccImplRoot->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
|
|
//Get "pParent" acc again.
|
|
xParentAccImpl = GetContextImpl(pParent,false);
|
|
}
|
|
else
|
|
{
|
|
//directly create this acc para .
|
|
xParentAccImpl = GetContextImpl(pParent);//Should Create.
|
|
|
|
const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pParent), GetShell()->IsPreview() );
|
|
|
|
::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false);
|
|
if(xParentAccImplRoot.is())
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::CHILD;
|
|
aEvent.NewValue <<= uno::Reference< XAccessible>(xParentAccImpl);
|
|
aEvent.IndexHint = -1;
|
|
xParentAccImplRoot->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
if (xParentAccImpl.is())
|
|
{
|
|
uno::Reference< XAccessible> xAccShape =
|
|
GetContext(pObj,xParentAccImpl.get());
|
|
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::CHILD;
|
|
aEvent.NewValue <<= xAccShape;
|
|
aEvent.IndexHint = -1;
|
|
xParentAccImpl->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_setParaAdd.clear();
|
|
m_setParaRemove.clear();
|
|
if( xOldAcc.is() && xOldAcc != xAcc )
|
|
InvalidateCursorPosition( xOldAcc );
|
|
if( bOldShapeSelected || bShapeSelected )
|
|
InvalidateShapeSelection();
|
|
if( xAcc.is() )
|
|
InvalidateCursorPosition( xAcc );
|
|
|
|
InvalidateShapeInParaSelection();
|
|
|
|
for (SwAccessibleParagraph* pAccPara : m_setParaRemove)
|
|
{
|
|
if (pAccPara && !pAccPara->IsDisposed() &&
|
|
pAccPara->getSelectedAccessibleChildCount() == 0 &&
|
|
pAccPara->getSelectedText().getLength() == 0)
|
|
{
|
|
if(pAccPara->SetSelectedState(false))
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE;
|
|
pAccPara->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
for (SwAccessibleParagraph* pAccPara : m_setParaAdd)
|
|
{
|
|
if(pAccPara && pAccPara->SetSelectedState(true))
|
|
{
|
|
AccessibleEventObject aEvent;
|
|
aEvent.EventId = AccessibleEventId::SELECTION_CHANGED;
|
|
pAccPara->FireAccessibleEvent( aEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateFocus()
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if(GetShell()->IsPreview())
|
|
{
|
|
rtl::Reference<SwAccessibleContext> xDocView = GetDocumentView_(true);
|
|
assert(xDocView.is());
|
|
xDocView->InvalidateFocus();
|
|
}
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc = mxCursorContext;
|
|
if( xAcc.is() )
|
|
{
|
|
xAcc->InvalidateFocus();
|
|
}
|
|
else
|
|
{
|
|
DoInvalidateShapeSelection(true);
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::SetCursorContext(
|
|
const ::rtl::Reference < SwAccessibleContext >& rCursorContext )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
mxCursorContext = rCursorContext.get();
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateEditableStates( const SwFrame* _pFrame )
|
|
{
|
|
// Start with the frame or the first upper that is accessible
|
|
SwAccessibleChild aFrameOrObj( _pFrame );
|
|
while( aFrameOrObj.GetSwFrame() &&
|
|
!aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper();
|
|
if( !aFrameOrObj.GetSwFrame() )
|
|
aFrameOrObj = GetShell()->GetLayout();
|
|
|
|
uno::Reference< XAccessible > xAcc( GetContext( aFrameOrObj.GetSwFrame() ) );
|
|
SwAccessibleContext *pAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() );
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES,
|
|
pAccImpl,
|
|
SwAccessibleChild(pAccImpl->GetFrame()),
|
|
AccessibleStates::EDITABLE );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
pAccImpl->InvalidateStates( AccessibleStates::EDITABLE );
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateRelationSet_( const SwFrame* pFrame,
|
|
bool bFrom )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
// first, see if this frame is accessible, and if so, get the respective
|
|
SwAccessibleChild aFrameOrObj( pFrame );
|
|
if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
return;
|
|
|
|
if (!mpFrameMap)
|
|
return;
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
xAcc = (*aIter).second;
|
|
}
|
|
|
|
// deliver event directly, or queue event
|
|
if( !xAcc.is() )
|
|
return;
|
|
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES,
|
|
xAcc.get(), SwAccessibleChild(pFrame),
|
|
( bFrom
|
|
? AccessibleStates::RELATION_FROM
|
|
: AccessibleStates::RELATION_TO ) );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
xAcc->InvalidateRelation( bFrom
|
|
? AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED
|
|
: AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED );
|
|
}
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateRelationSet( const SwFrame* pMaster,
|
|
const SwFrame* pFollow )
|
|
{
|
|
InvalidateRelationSet_( pMaster, false );
|
|
InvalidateRelationSet_( pFollow, true );
|
|
}
|
|
|
|
// invalidation of CONTENT_FLOW_FROM/_TO relation of a paragraph
|
|
void SwAccessibleMap::InvalidateParaFlowRelation( const SwTextFrame& _rTextFrame,
|
|
const bool _bFrom )
|
|
{
|
|
InvalidateRelationSet_( &_rTextFrame, _bFrom );
|
|
}
|
|
|
|
// invalidation of text selection of a paragraph
|
|
void SwAccessibleMap::InvalidateParaTextSelection( const SwTextFrame& _rTextFrame )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
// first, see if this frame is accessible, and if so, get the respective
|
|
SwAccessibleChild aFrameOrObj( &_rTextFrame );
|
|
if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
return;
|
|
|
|
if (!mpFrameMap)
|
|
return;
|
|
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
xAcc = (*aIter).second;
|
|
}
|
|
|
|
// deliver event directly, or queue event
|
|
if( !xAcc.is() )
|
|
return;
|
|
|
|
if( GetShell()->ActionPend() )
|
|
{
|
|
SwAccessibleEvent_Impl aEvent(
|
|
SwAccessibleEvent_Impl::CARET_OR_STATES,
|
|
xAcc.get(),
|
|
SwAccessibleChild( &_rTextFrame ),
|
|
AccessibleStates::TEXT_SELECTION_CHANGED );
|
|
AppendEvent( aEvent );
|
|
}
|
|
else
|
|
{
|
|
FireEvents();
|
|
xAcc->InvalidateTextSelection();
|
|
}
|
|
}
|
|
|
|
sal_Int32 SwAccessibleMap::GetChildIndex( const SwFrame& rParentFrame,
|
|
vcl::Window& rChild ) const
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
sal_Int32 nIndex( -1 );
|
|
|
|
SwAccessibleChild aFrameOrObj( &rParentFrame );
|
|
if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
|
|
if( mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( aFrameOrObj.GetSwFrame() );
|
|
if( aIter != mpFrameMap->end() )
|
|
{
|
|
xAcc = (*aIter).second;
|
|
}
|
|
}
|
|
|
|
if( xAcc.is() )
|
|
nIndex = xAcc->GetChildIndex( const_cast<SwAccessibleMap&>(*this),
|
|
SwAccessibleChild( &rChild ) );
|
|
}
|
|
|
|
return nIndex;
|
|
}
|
|
|
|
void SwAccessibleMap::UpdatePreview( const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages,
|
|
const Fraction& _rScale,
|
|
const SwPageFrame* _pSelectedPageFrame,
|
|
const Size& _rPreviewWinSize )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
assert(GetShell()->IsPreview() && "no preview?");
|
|
assert(mpPreview != nullptr && "no preview data?");
|
|
|
|
mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize );
|
|
|
|
// propagate change of VisArea through the document's
|
|
// accessibility tree; this will also send appropriate scroll
|
|
// events
|
|
SwAccessibleContext* pDoc =
|
|
GetContextImpl( GetShell()->GetLayout() ).get();
|
|
static_cast<SwAccessibleDocumentBase*>( pDoc )->SetVisArea();
|
|
|
|
rtl::Reference < SwAccessibleContext > xOldAcc = mxCursorContext;
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
|
|
const SwPageFrame *pSelPage = mpPreview->GetSelPage();
|
|
if( pSelPage && mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter =
|
|
mpFrameMap->find( pSelPage );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
}
|
|
|
|
if( xOldAcc.is() && xOldAcc != xAcc )
|
|
InvalidateCursorPosition( xOldAcc );
|
|
if( xAcc.is() )
|
|
InvalidateCursorPosition( xAcc );
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidatePreviewSelection( sal_uInt16 nSelPage )
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
assert(GetShell()->IsPreview());
|
|
assert(mpPreview != nullptr);
|
|
|
|
mpPreview->InvalidateSelection( GetShell()->GetLayout()->GetPageByPageNum( nSelPage ) );
|
|
|
|
rtl::Reference < SwAccessibleContext > xOldAcc = mxCursorContext;
|
|
rtl::Reference < SwAccessibleContext > xAcc;
|
|
|
|
const SwPageFrame *pSelPage = mpPreview->GetSelPage();
|
|
if( pSelPage && mpFrameMap )
|
|
{
|
|
SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pSelPage );
|
|
if( aIter != mpFrameMap->end() )
|
|
xAcc = (*aIter).second;
|
|
}
|
|
|
|
if( xOldAcc.is() && xOldAcc != xAcc )
|
|
InvalidateCursorPosition( xOldAcc );
|
|
if( xAcc.is() )
|
|
InvalidateCursorPosition( xAcc );
|
|
}
|
|
|
|
bool SwAccessibleMap::IsPageSelected( const SwPageFrame *pPageFrame ) const
|
|
{
|
|
return mpPreview && mpPreview->GetSelPage() == pPageFrame;
|
|
}
|
|
|
|
void SwAccessibleMap::FireEvents()
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
{
|
|
osl::MutexGuard aGuard( maEventMutex );
|
|
if( mpEvents )
|
|
{
|
|
if (mpEvents->IsFiring())
|
|
{
|
|
return; // prevent recursive FireEvents()
|
|
}
|
|
|
|
mpEvents->SetFiring();
|
|
mpEvents->MoveMissingXAccToEnd();
|
|
for( auto const& aEvent : *mpEvents )
|
|
FireEvent(aEvent);
|
|
|
|
mpEventMap.reset();
|
|
mpEvents.reset();
|
|
}
|
|
}
|
|
mvShapes.clear();
|
|
}
|
|
|
|
tools::Rectangle SwAccessibleMap::GetVisibleArea() const
|
|
{
|
|
return o3tl::convert( GetVisArea().SVRect(), o3tl::Length::twip, o3tl::Length::mm100 );
|
|
}
|
|
|
|
// Convert a MM100 value relative to the document root into a pixel value
|
|
// relative to the screen!
|
|
Point SwAccessibleMap::LogicToPixel( const Point& rPoint ) const
|
|
{
|
|
Point aPoint = o3tl::toTwips( rPoint, o3tl::Length::mm100 );
|
|
if (const vcl::Window* pWin = GetShell()->GetWin())
|
|
{
|
|
const MapMode aMapMode = GetMapMode(aPoint);
|
|
aPoint = pWin->LogicToPixel( aPoint, aMapMode );
|
|
aPoint = Point(pWin->OutputToAbsoluteScreenPixel( aPoint ));
|
|
}
|
|
|
|
return aPoint;
|
|
}
|
|
|
|
Size SwAccessibleMap::LogicToPixel( const Size& rSize ) const
|
|
{
|
|
Size aSize( o3tl::toTwips( rSize, o3tl::Length::mm100 ) );
|
|
if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev())
|
|
{
|
|
const MapMode aMapMode = GetMapMode(Point(0, 0));
|
|
aSize = pWin->LogicToPixel( aSize, aMapMode );
|
|
}
|
|
|
|
return aSize;
|
|
}
|
|
|
|
bool SwAccessibleMap::ReplaceChild (
|
|
::accessibility::AccessibleShape* pCurrentChild,
|
|
const uno::Reference< drawing::XShape >& _rxShape,
|
|
const tools::Long /*_nIndex*/,
|
|
const ::accessibility::AccessibleShapeTreeInfo& /*_rShapeTreeInfo*/
|
|
)
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
const SdrObject *pObj = nullptr;
|
|
if( mpShapeMap )
|
|
{
|
|
SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin();
|
|
SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend();
|
|
while( aIter != aEndIter && !pObj )
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if( xAcc.get() == pCurrentChild )
|
|
{
|
|
pObj = (*aIter).first;
|
|
}
|
|
++aIter;
|
|
}
|
|
}
|
|
if( !pObj )
|
|
return false;
|
|
|
|
uno::Reference < drawing::XShape > xShape( _rxShape ); // keep reference to shape, because
|
|
// we might be the only one that
|
|
// holds it.
|
|
// Also get keep parent.
|
|
uno::Reference < XAccessible > xParent( pCurrentChild->getAccessibleParent() );
|
|
pCurrentChild = nullptr; // will be released by dispose
|
|
A11yDispose( nullptr, pObj, nullptr );
|
|
|
|
if( !mpShapeMap )
|
|
mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this ));
|
|
|
|
// create the new child
|
|
::accessibility::ShapeTypeHandler& rShapeTypeHandler =
|
|
::accessibility::ShapeTypeHandler::Instance();
|
|
::accessibility::AccessibleShapeInfo aShapeInfo(
|
|
xShape, xParent, this );
|
|
rtl::Reference< ::accessibility::AccessibleShape> pReplacement(
|
|
rShapeTypeHandler.CreateAccessibleObject (
|
|
aShapeInfo, mpShapeMap->GetInfo() ));
|
|
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( pReplacement );
|
|
if( xAcc.is() )
|
|
{
|
|
pReplacement->Init();
|
|
|
|
SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj );
|
|
if( aIter != mpShapeMap->end() )
|
|
{
|
|
(*aIter).second = xAcc.get();
|
|
}
|
|
else
|
|
{
|
|
mpShapeMap->emplace( pObj, xAcc );
|
|
}
|
|
}
|
|
|
|
SwRect aEmptyRect;
|
|
InvalidatePosOrSize( nullptr, pObj, nullptr, aEmptyRect );
|
|
|
|
return true;
|
|
}
|
|
|
|
//Get the accessible control shape from the model object, here model object is with XPropertySet type
|
|
::accessibility::AccessibleControlShape * SwAccessibleMap::GetAccControlShapeFromModel(css::beans::XPropertySet* pSet)
|
|
{
|
|
if( mpShapeMap )
|
|
{
|
|
SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin();
|
|
SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend();
|
|
while( aIter != aEndIter)
|
|
{
|
|
rtl::Reference < ::accessibility::AccessibleShape > xAcc( (*aIter).second );
|
|
if(xAcc && ::accessibility::ShapeTypeHandler::Instance().GetTypeId (xAcc->GetXShape()) == ::accessibility::DRAWING_CONTROL)
|
|
{
|
|
::accessibility::AccessibleControlShape *pCtlAccShape = static_cast < ::accessibility::AccessibleControlShape* >(xAcc.get());
|
|
if (pCtlAccShape->GetControlModel() == pSet)
|
|
return pCtlAccShape;
|
|
}
|
|
++aIter;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
css::uno::Reference< XAccessible >
|
|
SwAccessibleMap::GetAccessibleCaption (const css::uno::Reference< css::drawing::XShape >&)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
Point SwAccessibleMap::PixelToCore( const Point& rPoint ) const
|
|
{
|
|
Point aPoint;
|
|
if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev())
|
|
{
|
|
const MapMode aMapMode = GetMapMode(rPoint);
|
|
aPoint = pWin->PixelToLogic( rPoint, aMapMode );
|
|
}
|
|
return aPoint;
|
|
}
|
|
|
|
static tools::Long lcl_CorrectCoarseValue(tools::Long aCoarseValue, tools::Long aFineValue,
|
|
tools::Long aRefValue, bool bToLower)
|
|
{
|
|
tools::Long aResult = aCoarseValue;
|
|
|
|
if (bToLower)
|
|
{
|
|
if (aFineValue < aRefValue)
|
|
aResult -= 1;
|
|
}
|
|
else
|
|
{
|
|
if (aFineValue > aRefValue)
|
|
aResult += 1;
|
|
}
|
|
|
|
return aResult;
|
|
}
|
|
|
|
static void lcl_CorrectRectangle(tools::Rectangle & rRect,
|
|
const tools::Rectangle & rSource,
|
|
const tools::Rectangle & rInGrid)
|
|
{
|
|
rRect.SetLeft( lcl_CorrectCoarseValue(rRect.Left(), rSource.Left(),
|
|
rInGrid.Left(), false) );
|
|
rRect.SetTop( lcl_CorrectCoarseValue(rRect.Top(), rSource.Top(),
|
|
rInGrid.Top(), false) );
|
|
rRect.SetRight( lcl_CorrectCoarseValue(rRect.Right(), rSource.Right(),
|
|
rInGrid.Right(), true) );
|
|
rRect.SetBottom( lcl_CorrectCoarseValue(rRect.Bottom(), rSource.Bottom(),
|
|
rInGrid.Bottom(), true) );
|
|
}
|
|
|
|
tools::Rectangle SwAccessibleMap::CoreToPixel( const SwRect& rRect ) const
|
|
{
|
|
tools::Rectangle aRect;
|
|
if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev())
|
|
{
|
|
const MapMode aMapMode = GetMapMode(rRect.TopLeft());
|
|
aRect = pWin->LogicToPixel( rRect.SVRect(), aMapMode );
|
|
|
|
tools::Rectangle aTmpRect = pWin->PixelToLogic( aRect, aMapMode );
|
|
lcl_CorrectRectangle(aRect, rRect.SVRect(), aTmpRect);
|
|
}
|
|
|
|
return aRect;
|
|
}
|
|
|
|
/** get mapping mode for LogicToPixel and PixelToLogic conversions
|
|
|
|
Method returns mapping mode of current output device and adjusts it,
|
|
if the shell is in page/print preview.
|
|
Necessary, because <PreviewAdjust(..)> changes mapping mode at current
|
|
output device for mapping logic document positions to page preview window
|
|
positions and vice versa and doesn't take care to recover its changes.
|
|
*/
|
|
MapMode SwAccessibleMap::GetMapMode(const Point& _rPoint) const
|
|
{
|
|
MapMode aMapMode = GetShell()->GetWin()->GetMapMode();
|
|
if( GetShell()->IsPreview() )
|
|
{
|
|
assert(mpPreview != nullptr);
|
|
mpPreview->AdjustMapMode( aMapMode, _rPoint );
|
|
}
|
|
return aMapMode;
|
|
}
|
|
|
|
Size SwAccessibleMap::GetPreviewPageSize(sal_uInt16 const nPreviewPageNum) const
|
|
{
|
|
assert(mpVSh->IsPreview());
|
|
assert(mpPreview != nullptr);
|
|
return mpVSh->PagePreviewLayout()->GetPreviewPageSizeByPageNum(nPreviewPageNum);
|
|
}
|
|
|
|
/** method to build up a new data structure of the accessible paragraphs,
|
|
which have a selection
|
|
Important note: method has to be used inside a mutual exclusive section
|
|
*/
|
|
std::unique_ptr<SwAccessibleSelectedParas_Impl> SwAccessibleMap::BuildSelectedParas()
|
|
{
|
|
// no accessible contexts, no selection
|
|
if ( !mpFrameMap )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// get cursor as an instance of its base class <SwPaM>
|
|
SwPaM* pCursor( nullptr );
|
|
{
|
|
SwCursorShell* pCursorShell = dynamic_cast<SwCursorShell*>(GetShell());
|
|
if ( pCursorShell )
|
|
{
|
|
SwFEShell* pFEShell = dynamic_cast<SwFEShell*>(pCursorShell);
|
|
if ( !pFEShell ||
|
|
( !pFEShell->IsFrameSelected() &&
|
|
pFEShell->IsObjSelected() == 0 ) )
|
|
{
|
|
// get cursor without updating an existing table cursor.
|
|
pCursor = pCursorShell->GetCursor( false );
|
|
}
|
|
}
|
|
}
|
|
// no cursor, no selection
|
|
if ( !pCursor )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<SwAccessibleSelectedParas_Impl> pRetSelectedParas;
|
|
|
|
// loop on all cursors
|
|
SwPaM* pRingStart = pCursor;
|
|
do {
|
|
|
|
// for a selection the cursor has to have a mark.
|
|
// for safety reasons assure that point and mark are in text nodes
|
|
if ( pCursor->HasMark() &&
|
|
pCursor->GetPoint()->GetNode().IsTextNode() &&
|
|
pCursor->GetMark()->GetNode().IsTextNode() )
|
|
{
|
|
auto [pStartPos, pEndPos] = pCursor->StartEnd(); // SwPosition*
|
|
// loop on all text nodes inside the selection
|
|
SwNodeIndex aIdx( pStartPos->GetNode() );
|
|
for ( ; aIdx.GetIndex() <= pEndPos->GetNodeIndex(); ++aIdx )
|
|
{
|
|
SwTextNode* pTextNode( aIdx.GetNode().GetTextNode() );
|
|
if ( pTextNode )
|
|
{
|
|
// loop on all text frames registered at the text node.
|
|
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode);
|
|
for( SwTextFrame* pTextFrame = aIter.First(); pTextFrame; pTextFrame = aIter.Next() )
|
|
{
|
|
unotools::WeakReference < SwAccessibleContext > xWeakAcc;
|
|
SwAccessibleContextMap_Impl::iterator aMapIter =
|
|
mpFrameMap->find( pTextFrame );
|
|
if( aMapIter != mpFrameMap->end() )
|
|
{
|
|
xWeakAcc = (*aMapIter).second;
|
|
SwAccessibleParaSelection aDataEntry(
|
|
sw::FrameContainsNode(*pTextFrame, pStartPos->GetNodeIndex())
|
|
? pTextFrame->MapModelToViewPos(*pStartPos)
|
|
: TextFrameIndex(0),
|
|
|
|
sw::FrameContainsNode(*pTextFrame, pEndPos->GetNodeIndex())
|
|
? pTextFrame->MapModelToViewPos(*pEndPos)
|
|
: TextFrameIndex(COMPLETE_STRING));
|
|
if ( !pRetSelectedParas )
|
|
{
|
|
pRetSelectedParas.reset(
|
|
new SwAccessibleSelectedParas_Impl);
|
|
}
|
|
// sw_redlinehide: should be idempotent for multiple nodes in a merged para
|
|
pRetSelectedParas->emplace( xWeakAcc, aDataEntry );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// prepare next turn: get next cursor in ring
|
|
pCursor = pCursor->GetNext();
|
|
} while ( pCursor != pRingStart );
|
|
|
|
return pRetSelectedParas;
|
|
}
|
|
|
|
void SwAccessibleMap::InvalidateTextSelectionOfAllParas()
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
// keep previously known selected paragraphs
|
|
std::unique_ptr<SwAccessibleSelectedParas_Impl> pPrevSelectedParas( std::move(mpSelectedParas) );
|
|
|
|
// determine currently selected paragraphs
|
|
mpSelectedParas = BuildSelectedParas();
|
|
|
|
// compare currently selected paragraphs with the previously selected
|
|
// paragraphs and submit corresponding TEXT_SELECTION_CHANGED events.
|
|
// first, search for new and changed selections.
|
|
// on the run remove selections from previously known ones, if they are
|
|
// also in the current ones.
|
|
if ( mpSelectedParas )
|
|
{
|
|
SwAccessibleSelectedParas_Impl::iterator aIter = mpSelectedParas->begin();
|
|
for ( ; aIter != mpSelectedParas->end(); ++aIter )
|
|
{
|
|
bool bSubmitEvent( false );
|
|
if ( !pPrevSelectedParas )
|
|
{
|
|
// new selection
|
|
bSubmitEvent = true;
|
|
}
|
|
else
|
|
{
|
|
SwAccessibleSelectedParas_Impl::iterator aPrevSelected =
|
|
pPrevSelectedParas->find( (*aIter).first );
|
|
if ( aPrevSelected != pPrevSelectedParas->end() )
|
|
{
|
|
// check, if selection has changed
|
|
if ( (*aIter).second.nStartOfSelection !=
|
|
(*aPrevSelected).second.nStartOfSelection ||
|
|
(*aIter).second.nEndOfSelection !=
|
|
(*aPrevSelected).second.nEndOfSelection )
|
|
{
|
|
// changed selection
|
|
bSubmitEvent = true;
|
|
}
|
|
pPrevSelectedParas->erase( aPrevSelected );
|
|
}
|
|
else
|
|
{
|
|
// new selection
|
|
bSubmitEvent = true;
|
|
}
|
|
}
|
|
|
|
if ( bSubmitEvent )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc( (*aIter).first );
|
|
if ( xAcc.is() && xAcc->GetFrame() )
|
|
{
|
|
const SwTextFrame* pTextFrame = xAcc->GetFrame()->DynCastTextFrame();
|
|
OSL_ENSURE( pTextFrame,
|
|
"<SwAccessibleMap::_SubmitTextSelectionChangedEvents()> - unexpected type of frame" );
|
|
if ( pTextFrame )
|
|
{
|
|
InvalidateParaTextSelection( *pTextFrame );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// second, handle previous selections - after the first step the data
|
|
// structure of the previously known only contains the 'old' selections
|
|
if ( !pPrevSelectedParas )
|
|
return;
|
|
|
|
SwAccessibleSelectedParas_Impl::iterator aIter = pPrevSelectedParas->begin();
|
|
for ( ; aIter != pPrevSelectedParas->end(); ++aIter )
|
|
{
|
|
rtl::Reference < SwAccessibleContext > xAcc( (*aIter).first );
|
|
if ( xAcc.is() && xAcc->GetFrame() )
|
|
{
|
|
const SwTextFrame* pTextFrame = xAcc->GetFrame()->DynCastTextFrame();
|
|
OSL_ENSURE( pTextFrame,
|
|
"<SwAccessibleMap::_SubmitTextSelectionChangedEvents()> - unexpected type of frame" );
|
|
if ( pTextFrame )
|
|
{
|
|
InvalidateParaTextSelection( *pTextFrame );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const SwRect& SwAccessibleMap::GetVisArea() const
|
|
{
|
|
assert(!GetShell()->IsPreview() || (mpPreview != nullptr));
|
|
|
|
return GetShell()->IsPreview()
|
|
? mpPreview->GetVisArea()
|
|
: GetShell()->VisArea();
|
|
}
|
|
|
|
bool SwAccessibleMap::IsDocumentSelAll()
|
|
{
|
|
return GetShell()->GetDoc()->IsPrepareSelAll();
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|