forked from amazingfate/loongoffice
With 16k column support in Calc enabled by default in
commit 4c5f8ccf0a2320432b8fe91add1dcadf54d9fd58
Date: Tue Mar 8 12:44:49 2022 +0100
change default Calc number of columns to 16384 (tdf#50916)
, the number of Calc cells in a spreadsheet is larger than
SAL_MAX_INT32, meaning that a 32-bit a11y child index is no more
enough and using it resulted in integer overflows in
methods handling corresponding Calc cells in the a11y layer.
This e.g. had the effect of the Orca and NVDA screen readers
not announcing focused or selected cells properly when their
a11y child index was out of the 32-bit integer range.
Switch the internal a11y child indices to 64 bit to
be able to handle this properly internally.
Since the platform APIs (at least AT-SPI on Linux and
IAccessible2 on Windows; from what I can see LO's macOS
a11y bridge doesn't directly expose the child index)
are still restricted to 32 bit, larger child indices
still cannot be exposed via the platform APIs.
As a consequence, use of the the IAccessible2 and
AT-SPI methods that use the child index remains
problematic in those cases where the child index
is larger. However, as an alternative to using the
AT-SPI Table interface and the IAccessibleTable/
IAccessibleTable2 interfaces with the child index
to retrieve information about a specific cell,
both AT-SPI and IAccessible2 also provide interfaces
to retrieve that information directly
from the cell object (TableCell interface for AT-SPI,
IAccessibleTableCell for IAccessible2).
Those interfaces are already implemented/exposed
for winaccessibility (s. `CAccTable`) and the
qt5/qt6/kf5 VCL plugins (s. the `QAccessibleTableCellInterface`
methods implemented in `QtAccessibleInterface`).
With the switch to 64-bit internal a11y child indices,
these now behave correctly for cells with a child
index that doesn't fit into 32 bit as well.
NVDA on Windows already uses the IAccessibleTableCell
interface and thus announcing focused cells works fine
with this change in place.
Orca on Linux currently doesn't make use of the AT-SPI
TableCell interface yet, but with a suggested change to
do so [1], announcement of selected cells works
with the qt6 VCL plugin with a current qtbase dev branch
as well - when combined with the suggested changes
to implement support for the AT-SPI TableCell interface
in Qt [2] [3] and the LO change based on that [4] and
a fix for a nullptr dereference [5].
The gtk3 VCL plugin doesn't expose the AT-SPI
TableCell interface yet, but once it does so
(via `AtkTableCell`), it also works with the
suggested Orca change [1] in place.
(Adding that is planned for an upcoming change,
works with a local WIP patch.)
For handling return values that are larger than what
platform APIs support, the following approach has
been chosen for now:
1) When the return value is for the count of
(selected) children, the maximum value N
supported by the platform API is returned.
(This is what `ScAccessibleTableBase::getAccessibleChildCount`
did previously.)
The first N elements can be accessed by their
actual (selection) indices.
2) When the return value is the child/cell index,
-2 is returned for objects whose index is greater
than the maximum value supported by the platform
API.
Using a non-negative value would mean that the
index would refer to *another* actually existing
child. A child index of -1 on the other hand
tends to be interpreted as "child is invalid" or
"object isn't actually a child of its (previous)
parent any more)". For the Orca case, this would
result in objects with a child index of -1
not being announced, as they are considered
"zombies" [6].
What's still somewhat problematic is the case where
more than 2^31 children are *selected*, since access
to those children still happens by the index into
the selection in the platform APIs, and not all
selected items are accessible this way.
(Screen readers usually just retrieve
the first and last element from the selection and
announce those.)
Orca already seems to apply different handling for the
case for fully selected rows and columns, so
"All cells selected" or "Columns ... to ... selected"
is announced just fine even if more than 2^31
cells are selected.
(Side note: While Microsoft User Interface
Automation - UIA - also uses 32-bit indices, it also
has specific methods in the ISelectionProvider2
interface that allow to explicitly retrieve the
first and last selected item,
`ISelectionProvider2::get_FirstSelectedItem` and
`ISelectionProvider2::get_LastSelectedItem`, but
we currently don't support UIA on Windows.)
Bound checks at the beginning of the methods from the
`XAccessibleContext`, `XAccessibleSelection` and
`XAccessibleTable` interfaces that take a child index
(or in helper methods called by those) should generally
already prevent too large indices from being passed to
the methods in the lower layer code that take smaller
integer types. Such bound checking has been
been added in various places where it wasn't present yet.
If there any remaining issues of this
kind that show after this commit, they can probably be
solved in a similar way (s.e.g. the change to
`AccessibleBrowseBox::getAccessibleChild` in this
commit).
A few asserts were also added at
places where my understanding is that values shouldn't
be larger than what is supported by a called method
anyway.
A test case will be added in a following change.
[1] https://gitlab.gnome.org/GNOME/orca/-/merge_requests/131
[2] https://codereview.qt-project.org/c/qt/qtbase/+/428566
[3] https://codereview.qt-project.org/c/qt/qtbase/+/428567
[4] https://gerrit.libreoffice.org/c/core/+/138750
[5] https://codereview.qt-project.org/c/qt/qtbase/+/430157
[6] 82c8542002/src/orca/script_utilities.py (L5155)
Change-Id: I3af590c988b0e6754fc72545918412f39e8fea07
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139258
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
375 lines
14 KiB
C++
375 lines
14 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 .
|
|
*/
|
|
|
|
#ifndef INCLUDED_SVX_ACCESSIBLETEXTHELPER_HXX
|
|
#define INCLUDED_SVX_ACCESSIBLETEXTHELPER_HXX
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include <com/sun/star/uno/Reference.hxx>
|
|
#include <sal/types.h>
|
|
#include <svx/svxdllapi.h>
|
|
|
|
namespace com::sun::star
|
|
{
|
|
namespace accessibility
|
|
{
|
|
class XAccessible;
|
|
}
|
|
namespace accessibility
|
|
{
|
|
class XAccessibleEventListener;
|
|
}
|
|
namespace awt
|
|
{
|
|
struct Point;
|
|
}
|
|
}
|
|
|
|
class Point;
|
|
class SvxEditSource;
|
|
|
|
namespace accessibility
|
|
{
|
|
class AccessibleTextHelper_Impl;
|
|
|
|
/** Helper class for objects containing EditEngine/Outliner text
|
|
|
|
This class provides the methods from the XAccessibleContext,
|
|
XAccessibleEventBroadcaster and XAccessibleComponent
|
|
interfaces, that are common to all accessible objects
|
|
containing an edit engine.
|
|
|
|
The text contained in the EditEngine/Outliner is presented as
|
|
children of this class, namely for every text paragraph a
|
|
AccessibleEditableTextPara child object is generated. As this
|
|
class manages these children for itself, it has to send out
|
|
AccessibleEventId::CHILD events on your
|
|
behalf. Thus, you must forward every call to your
|
|
addEventListener()/removeEventListener() methods to the
|
|
AccessibleTextHelper (methods
|
|
AddEventListener/RemoveEventListener), otherwise none or not
|
|
everyone of your event listener will notice child changes.
|
|
|
|
You have to implement the SvxEditSource, SvxTextForwarder,
|
|
SvxViewForwarder and SvxEditViewForwarder interfaces in order
|
|
to enable your object to cooperate with this
|
|
class. SvxTextForwarder encapsulates the fact that text
|
|
objects do not necessarily have an EditEngine at their
|
|
disposal, SvxViewForwarder and SvxEditViewForwarder do the
|
|
same for the document and the edit view. The three mentioned
|
|
forwarder objects are not stored by the AccessibleTextHelper,
|
|
but fetched every time from the SvxEditSource. So you are best
|
|
off making your SvxEditSource::Get*Forwarder methods cache the
|
|
current forwarder.
|
|
|
|
To support changes in edit mode or conversion of fixed text
|
|
into EditEngine text, you can change the SvxEditSource this
|
|
class is referring to. This might render all children invalid
|
|
and change the child count, since the AccessibleTextHelper
|
|
reinitializes itself from scratch.
|
|
|
|
This class registers itself at the SvxEditSource as a state
|
|
listener and manages the state of its children (i.e. the
|
|
paragraphs). See the method documentation of
|
|
AccessibleTextHelper::SetEditSource for the expected
|
|
events. Generally, be prepared that when sending any of these
|
|
events via SvxEditSource::GetBroadcaster() broadcaster, the
|
|
AccessibleTextHelper will call the SvxEditSource and their
|
|
forwarder to update it's state. Avoid being inconsistent in
|
|
the facts you tell in the events, e.g. when sending a
|
|
SfxHintId::TextParaInserted event, the
|
|
SvxEditSource::GetTextForwarder().GetParagraphCount() should
|
|
already include the newly inserted paragraph.
|
|
|
|
@attention All public methods must not be called with any
|
|
mutex hold, except when calling from the main thread (with
|
|
holds the solar mutex), unless stated otherwise. This is
|
|
because they themselves might need the solar mutex in addition
|
|
to the object mutex, and the ordering of the locking must be:
|
|
first solar mutex, then object mutex. Furthermore, state
|
|
change events might be fired internally.
|
|
|
|
@derive Use this class in an aggregation and forward, or
|
|
derive from it and overwrite.
|
|
too.
|
|
|
|
@see SvxEditSource
|
|
@see SvxTextForwarder
|
|
@see SvxViewForwarder
|
|
@see SvxEditViewForwarder
|
|
*/
|
|
class SVX_DLLPUBLIC AccessibleTextHelper final
|
|
{
|
|
public:
|
|
typedef ::std::vector<sal_Int16> VectorOfStates;
|
|
|
|
/** Create accessible text object for given edit source
|
|
|
|
@param pEditSource
|
|
The edit source to use. Object ownership is transferred
|
|
from the caller to the callee. The object listens on the
|
|
SvxEditSource for object disposal, so no provisions have
|
|
to be taken if the caller destroys the data (e.g. the
|
|
model) contained in the given SvxEditSource.
|
|
|
|
*/
|
|
explicit AccessibleTextHelper(::std::unique_ptr<SvxEditSource>&& pEditSource);
|
|
|
|
~AccessibleTextHelper();
|
|
|
|
AccessibleTextHelper(const AccessibleTextHelper&) = delete;
|
|
AccessibleTextHelper& operator=(const AccessibleTextHelper&) = delete;
|
|
|
|
/** Query the current edit source
|
|
|
|
@attention This method returns by reference, so you are
|
|
responsible for serialization (typically, you acquired the
|
|
solar mutex when calling this method). Thus, the method
|
|
should only be called from the main office thread.
|
|
|
|
*/
|
|
const SvxEditSource& GetEditSource() const;
|
|
|
|
/** Set the current edit source
|
|
|
|
@attention Might fire state change events, therefore,
|
|
don't hold any mutex except solar mutex, which you are
|
|
required to lock before. This method should only be called
|
|
from the main office thread.
|
|
|
|
The EditSource set here is required to broadcast out the
|
|
following hints: SfxHintId::EditSourceParasMoved,
|
|
SfxHintId::EditSourceSelectionChanged, SfxHintId::TextModified,
|
|
SfxHintId::TextParaInserted, SfxHintId::TextParaRemoved,
|
|
SfxHintId::TextHeightChanged,
|
|
SfxHintId::TextViewScrolled. Otherwise, not all state changes
|
|
will get noticed by the accessibility object. Further
|
|
more, when the corresponding core object or the model is
|
|
dying, either the edit source must be set to NULL or it
|
|
has to broadcast a SfxHintId::Dying hint.
|
|
|
|
If the SvxEditSource's managed text can change between
|
|
edit/non-edit mode (i.e. there are times when
|
|
SvxEditSource::GetEditViewForwarder(sal_False) returns
|
|
NULL), then the two additional hints are required:
|
|
SdrHintKind::BeginEdit and SdrHintKind::EndEdit. When the
|
|
AccessibleTextHelper receives a SdrHintKind::BeginEdit, it expects
|
|
the SvxEditSource already in edit mode. On a SdrHintKind::EndEdit,
|
|
edit mode must already been left. The rationale for these
|
|
events are the fact that focus and selection have to be
|
|
updated in edit mode, and completely relinquished and
|
|
reset to the parent (for the focus) in non-edit mode.
|
|
|
|
This class does not have a dispose method, since it is not
|
|
a UNO component. Nevertheless, it holds C++ references to
|
|
several core objects, so you should issue a
|
|
SetEditSource(::std::unique_ptr<SvxEditSource>()) in
|
|
your dispose() method.
|
|
|
|
@param pEditSource
|
|
The new edit source to set. Object ownership is transferred
|
|
from the caller to the callee.
|
|
*/
|
|
void SetEditSource(::std::unique_ptr<SvxEditSource>&& pEditSource);
|
|
|
|
/** Set the event source
|
|
|
|
You should set the event source before registering any
|
|
event listener and before requesting any child. Children
|
|
of this object receive the event source as their parent
|
|
accessible object. That is, the event source is best set
|
|
in your object's init method.
|
|
|
|
@attention When setting a reference here, you should call
|
|
Dispose() when you as the owner are disposing, since until
|
|
then this object will hold that reference
|
|
|
|
@param rInterface
|
|
The interface that should be set as the source for
|
|
accessibility events sent by this object.
|
|
*/
|
|
void SetEventSource(const css::uno::Reference<css::accessibility::XAccessible>& rInterface);
|
|
|
|
/** Set offset of EditEngine/Outliner from parent
|
|
|
|
If the origin of the underlying EditEngine/Outliner does
|
|
not correspond to the upper left corner of the object
|
|
using this class, you have to specify the offset.
|
|
|
|
@attention Might fire state change events, therefore,
|
|
don't hold any mutex except solar mutex, which you are
|
|
required to lock before. This method should only be called
|
|
from the main office thread.
|
|
|
|
@param rPoint
|
|
The offset in screen coordinates (i.e. pixel)
|
|
*/
|
|
void SetOffset(const Point& rPoint);
|
|
|
|
/** Set offset the object adds to all children's indices
|
|
|
|
This can be used if the owner of this object has children
|
|
handled by itself. Setting an offset different from 0
|
|
leads to this object mimicking that all its children are
|
|
within the range [nOffset, GetChildCount()+nOffset). That
|
|
means, GetChild() also expects the index to be in this
|
|
range.
|
|
|
|
@attention Might fire state change events, therefore,
|
|
don't hold any mutex except solar mutex, which you are
|
|
required to lock before. This method should only be called
|
|
from the main office thread.
|
|
|
|
@param nOffset
|
|
The offset to add to every children's index.
|
|
*/
|
|
void SetStartIndex(sal_Int32 nOffset);
|
|
|
|
/** Query offset the object adds to all children's indices
|
|
|
|
@return the offset to add to every children's index.
|
|
*/
|
|
sal_Int32 GetStartIndex() const;
|
|
|
|
/** Sets a bitset of additional accessible states.
|
|
|
|
The states are passed to every created child object
|
|
(text paragraph). The state values are defined in
|
|
css::accessibility::AccessibleStateType.
|
|
|
|
This function has to be called before querying for
|
|
any children (e.g. with GetChild()).
|
|
*/
|
|
void SetAdditionalChildStates(sal_Int64 rChildStates);
|
|
|
|
/** Update the visible children
|
|
|
|
@attention Might fire state change events, therefore,
|
|
don't hold any mutex except solar mutex, which you are
|
|
required to lock before. This method should only be called
|
|
from the main office thread.
|
|
|
|
This method reevaluates the visibility of all
|
|
children. Call this method if your visibility state has
|
|
changed somehow, e.g. if the visible area has changed and
|
|
the AccessibleTextHelper isn't notified internally
|
|
(e.g. via SfxHintId::TextViewScrolled). Normally, there should
|
|
not be a need to call this method.
|
|
*/
|
|
void UpdateChildren();
|
|
|
|
/** Drop all references and enter disposed state
|
|
|
|
This method drops all references to external objects (also
|
|
the event source reference set via SetEventSource()) and
|
|
sets the object into the disposed state (i.e. the methods
|
|
return default values or throw a uno::DisposedException
|
|
exception).
|
|
*/
|
|
void Dispose();
|
|
|
|
/** Set the focus state of the accessibility object
|
|
|
|
Since this class handles children which also might get the
|
|
focus, the user of this class is encouraged to delegate
|
|
focus handling. Whenever the focus state of the
|
|
surrounding object changes, this method has to be called.
|
|
|
|
The protocol of focus handling for a user of this class is
|
|
then to call SetFocus() with the appropriate focus state,
|
|
and HaveFocus() to determine the focus state you tell the
|
|
outside.
|
|
|
|
@attention Might fire state change events, therefore,
|
|
don't hold any mutex except solar mutex, which you are
|
|
required to lock before. This method should only be called
|
|
from the main office thread.
|
|
|
|
@param bHaveFocus
|
|
Whether we got or we lost the focus. Set to true if
|
|
focus is gotten, false otherwise.
|
|
|
|
@see HaveFocus()
|
|
*/
|
|
void SetFocus(bool bHaveFocus = true);
|
|
|
|
/** Query the focus state of the surrounding object
|
|
|
|
If focus handling is delegated to this class, determine
|
|
focus state with this method. Be prepared that even if you
|
|
set the focus with SetFocus(true), this method might
|
|
return false. This is the case if one of the children
|
|
actually got the focus.
|
|
|
|
@return the state of the focus ownership
|
|
*/
|
|
bool HaveFocus();
|
|
|
|
// XAccessibleContext child handling methods
|
|
|
|
/** Implements getAccessibleChildCount
|
|
|
|
@attention Don't call with locked mutexes. You may hold
|
|
the solar mutex, but this method acquires it anyway.
|
|
*/
|
|
sal_Int64 GetChildCount() const;
|
|
/** Implements getAccessibleChild
|
|
|
|
@attention Don't call with locked mutexes. You may hold
|
|
the solar mutex, but this method acquires it anyway.
|
|
*/
|
|
css::uno::Reference<css::accessibility::XAccessible> GetChild(sal_Int64 i);
|
|
|
|
// XAccessibleEventBroadcaster child related methods
|
|
|
|
/** Implements addEventListener
|
|
|
|
@attention Don't call with locked mutexes
|
|
*/
|
|
void AddEventListener(
|
|
const css::uno::Reference<css::accessibility::XAccessibleEventListener>& xListener);
|
|
/** Implements removeEventListener
|
|
|
|
@attention Don't call with locked mutexes
|
|
*/
|
|
void RemoveEventListener(
|
|
const css::uno::Reference<css::accessibility::XAccessibleEventListener>& xListener);
|
|
|
|
// XAccessibleComponent child related methods
|
|
|
|
/** Implements getAccessibleAt
|
|
|
|
@attention Don't call with locked mutexes. You may hold
|
|
the solar mutex, but this method acquires it anyway.
|
|
*/
|
|
css::uno::Reference<css::accessibility::XAccessible> GetAt(const css::awt::Point& aPoint);
|
|
|
|
private:
|
|
/// @dyn
|
|
const std::unique_ptr<AccessibleTextHelper_Impl> mpImpl;
|
|
};
|
|
|
|
} // end of namespace accessibility
|
|
|
|
#endif // INCLUDED_SVX_ACCESSIBLETEXTHELPER_HXX
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|