255 lines
9.8 KiB
C++
255 lines
9.8 KiB
C++
/* 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/. */
|
|
|
|
#ifndef mozilla_SelectionMovementUtils_h
|
|
#define mozilla_SelectionMovementUtils_h
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/EnumSet.h"
|
|
#include "mozilla/RangeBoundary.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/intl/BidiEmbeddingLevel.h"
|
|
#include "nsIFrame.h"
|
|
|
|
struct nsPrevNextBidiLevels;
|
|
|
|
namespace mozilla {
|
|
|
|
class PresShell;
|
|
enum class PeekOffsetOption : uint16_t;
|
|
|
|
namespace intl {
|
|
class BidiEmbeddingLevel;
|
|
}
|
|
|
|
struct MOZ_STACK_CLASS FrameAndOffset {
|
|
[[nodiscard]] nsIContent* GetFrameContent() const {
|
|
return mFrame ? mFrame->GetContent() : nullptr;
|
|
}
|
|
|
|
operator nsIFrame*() const { return mFrame; }
|
|
|
|
explicit operator bool() const { return !!mFrame; }
|
|
[[nodiscard]] bool operator!() const { return !mFrame; }
|
|
|
|
nsIFrame* operator->() const {
|
|
MOZ_ASSERT(mFrame);
|
|
return mFrame;
|
|
}
|
|
|
|
nsIFrame* mFrame = nullptr;
|
|
// The offset in mFrame->GetContent().
|
|
uint32_t mOffsetInFrameContent = 0;
|
|
};
|
|
|
|
struct MOZ_STACK_CLASS PrimaryFrameData : public FrameAndOffset {
|
|
// Whether the caret should be put before or after the point. This is valid
|
|
// only when mFrame is not nullptr.
|
|
CaretAssociationHint mHint{0}; // Before
|
|
};
|
|
|
|
struct MOZ_STACK_CLASS CaretFrameData : public PrimaryFrameData {
|
|
// The frame which is found only from a DOM point. This frame becomes
|
|
// different from mFrame when the point is around end of a line or
|
|
// at a bidi text boundary.
|
|
nsIFrame* mUnadjustedFrame = nullptr;
|
|
};
|
|
|
|
enum class ForceEditableRegion : bool { No, Yes };
|
|
|
|
class SelectionMovementUtils final {
|
|
public:
|
|
using PeekOffsetOptions = EnumSet<PeekOffsetOption>;
|
|
|
|
/**
|
|
* @brief Creates a new `RangeBoundary` which moves `aAmount` into
|
|
* `aDirection` from the input range boundary.
|
|
*
|
|
* @param aRangeBoundary The input range boundary.
|
|
* @param aDirection The direction into which the new boundary should be
|
|
* moved.
|
|
* @param aHint The `CaretAssociationHint` (is the caret before or
|
|
* after the boundary point)
|
|
* @param aCaretBidiLevel The `BidiEmbeddingLevel`.
|
|
* @param aAmount The amount which the range boundary should be
|
|
* moved.
|
|
* @param aOptions Additional options, see `PeekOffsetOption`.
|
|
* @param aAncestorLimiter The content node that limits where Selection may be
|
|
* expanded to.
|
|
*
|
|
* @return Returns a new `RangeBoundary` which is moved from `aRangeBoundary`
|
|
* by `aAmount` into `aDirection`.
|
|
*/
|
|
template <typename ParentType, typename RefType>
|
|
static Result<RangeBoundaryBase<ParentType, RefType>, nsresult>
|
|
MoveRangeBoundaryToSomewhere(
|
|
const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary,
|
|
nsDirection aDirection, CaretAssociationHint aHint,
|
|
intl::BidiEmbeddingLevel aCaretBidiLevel, nsSelectionAmount aAmount,
|
|
PeekOffsetOptions aOptions,
|
|
const dom::Element* aAncestorLimiter = nullptr);
|
|
|
|
/**
|
|
* Given a node and its child offset, return the nsIFrame and the offset into
|
|
* that frame.
|
|
*
|
|
* @param aNode input parameter for the node to look at
|
|
* @param aOffset offset into above node.
|
|
*/
|
|
static FrameAndOffset GetFrameForNodeOffset(const nsIContent* aNode,
|
|
uint32_t aOffset,
|
|
CaretAssociationHint aHint);
|
|
|
|
/**
|
|
* Return the first visible point in or at a leaf node in aRange or the first
|
|
* unselectable content if aRange starts from a selectable container. E.g.,
|
|
* return the start of the first visible `Text` or the position of the first
|
|
* visible leaf element. I.e., the result may be a good point to put a UI for
|
|
* showing something around the start boundary.
|
|
*
|
|
* NOTE: This won't return any boundary point in subtrees from the tree
|
|
* containing the start container of aRange due to ContentIteratorBase's
|
|
* limitation. See bug 2001511.
|
|
*
|
|
* @param aRange Must not be collapsed because this returns a point in aRange
|
|
* so that this requires the limitation of scanning forward.
|
|
* @return A position in a `Text` or a position at an element.
|
|
*/
|
|
[[nodiscard]] static RawRangeBoundary GetFirstVisiblePointAtLeaf(
|
|
const dom::AbstractRange& aRange);
|
|
|
|
/**
|
|
* Return the last visible point in or at a leaf node in aRange or the last
|
|
* unselectable content if aRange ends in a selectable container. E.g., return
|
|
* the end of the last visible `Text` or the position of the last visible leaf
|
|
* element. I.e., the result may be a good point to put a UI for showing
|
|
* something around the end boundary.
|
|
*
|
|
* NOTE: This won't return any boundary point in subtrees of the tree
|
|
* containing the end container of aRange due to ContentIteratorBase's
|
|
* limitation. See bug 2001511.
|
|
*
|
|
* @param aRange Must not be collapsed because this returns a point in aRange
|
|
* so that this requires the limitation of scanning forward.
|
|
* @return A position in a `Text` or a position at an element.
|
|
*/
|
|
[[nodiscard]] static RawRangeBoundary GetLastVisiblePointAtLeaf(
|
|
const dom::AbstractRange& aRange);
|
|
|
|
/**
|
|
* GetPrevNextBidiLevels will return the frames and associated Bidi levels of
|
|
* the characters logically before and after a (collapsed) selection.
|
|
*
|
|
* @param aNode is the node containing the selection
|
|
* @param aContentOffset is the offset of the selection in the node
|
|
* @param aJumpLines
|
|
* If true, look across line boundaries.
|
|
* If false, behave as if there were base-level frames at line edges.
|
|
* @param aAncestorLimiter If set, this refers only the descendants.
|
|
*
|
|
* @return A struct holding the before/after frame and the before/after
|
|
* level.
|
|
*
|
|
* At the beginning and end of each line there is assumed to be a frame with
|
|
* Bidi level equal to the paragraph embedding level.
|
|
*
|
|
* In these cases the before frame and after frame respectively will be
|
|
* nullptr.
|
|
*/
|
|
static nsPrevNextBidiLevels GetPrevNextBidiLevels(
|
|
nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint,
|
|
bool aJumpLines, const dom::Element* aAncestorLimiter);
|
|
|
|
/**
|
|
* PeekOffsetForCaretMove() only peek offset for caret move from the specified
|
|
* point of the normal selection. I.e., won't change selection ranges nor
|
|
* bidi information.
|
|
*/
|
|
static Result<PeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
|
|
nsIContent* aContent, uint32_t aOffset, nsDirection aDirection,
|
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel,
|
|
const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos,
|
|
PeekOffsetOptions aOptions, const dom::Element* aAncestorLimiter);
|
|
|
|
/**
|
|
* IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove()
|
|
* and CreateRangeExtendedToSomwhereFromNormalSelection(). This returns
|
|
* whether aAmount is intra line move or is crossing hard line break.
|
|
* This returns error if aMount is not supported by the methods.
|
|
*/
|
|
static Result<bool, nsresult> IsIntraLineCaretMove(
|
|
nsSelectionAmount aAmount) {
|
|
switch (aAmount) {
|
|
case eSelectCharacter:
|
|
case eSelectCluster:
|
|
case eSelectWord:
|
|
case eSelectWordNoSpace:
|
|
case eSelectBeginLine:
|
|
case eSelectEndLine:
|
|
case eSelectParagraph:
|
|
return true;
|
|
case eSelectLine:
|
|
return false;
|
|
default:
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a frame for considering caret geometry.
|
|
*
|
|
* @param aFrameSelection [optional] If this is specified and selection in
|
|
* aContent is not managed by the specified
|
|
* instance, return nullptr.
|
|
* @param aContentNode The content node where selection is collapsed.
|
|
* @param aOffset Collapsed position in aContentNode
|
|
* @param aFrameHint Caret association hint.
|
|
* @param aBidiLevel
|
|
* @param aForceEditableRegion Whether selection should be limited in
|
|
* editable region or not.
|
|
*/
|
|
static CaretFrameData GetCaretFrameForNodeOffset(
|
|
const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
|
|
uint32_t aOffset, CaretAssociationHint aFrameHint,
|
|
intl::BidiEmbeddingLevel aBidiLevel,
|
|
ForceEditableRegion aForceEditableRegion);
|
|
|
|
static bool AdjustFrameForLineStart(nsIFrame*& aFrame,
|
|
uint32_t& aFrameOffset);
|
|
|
|
/**
|
|
* Get primary frame and some other data for putting caret or extending
|
|
* selection at the point.
|
|
*/
|
|
static PrimaryFrameData GetPrimaryFrameForCaret(
|
|
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
|
|
|
private:
|
|
/**
|
|
* GetFrameFromLevel will scan in a given direction
|
|
* until it finds a frame with a Bidi level less than or equal to a given
|
|
* level. It will return the last frame before this.
|
|
*
|
|
* @param aPresContext is the context to use
|
|
* @param aFrameIn is the frame to start from
|
|
* @param aDirection is the direction to scan
|
|
* @param aBidiLevel is the level to search for
|
|
*/
|
|
static Result<nsIFrame*, nsresult> GetFrameFromLevel(
|
|
nsIFrame* aFrameIn, nsDirection aDirection,
|
|
intl::BidiEmbeddingLevel aBidiLevel);
|
|
|
|
// This is helper method for GetPrimaryFrameForCaret.
|
|
// If aVisual is true, this returns caret frame.
|
|
// If false, this returns primary frame.
|
|
static PrimaryFrameData GetPrimaryOrCaretFrameForNodeOffset(
|
|
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // #ifndef mozilla_SelectionMovementUtils_h
|