Chromium Code Reviews| Index: Source/core/editing/FrameSelection.cpp |
| diff --git a/Source/core/editing/FrameSelection.cpp b/Source/core/editing/FrameSelection.cpp |
| index 0cb682f643ce648cd3916e4093ffbb688e47085f..3d8d2d4d9d64d7f481b2c68911adb28d23955c5c 100644 |
| --- a/Source/core/editing/FrameSelection.cpp |
| +++ b/Source/core/editing/FrameSelection.cpp |
| @@ -93,6 +93,140 @@ static inline bool shouldAlwaysUseDirectionalSelection(LocalFrame* frame) |
| return !frame || frame->editor().behavior().shouldConsiderSelectionAsDirectional(); |
| } |
| +class GranularityStrategy { |
|
yosin_UTC9
2015/04/17 04:24:50
Is it better to move XXXStrategy to another file?
mfomitchev
2015/04/17 14:41:49
It's your call. The upside is reducing the file si
yosin_UTC9
2015/04/20 01:25:55
We've moved classes to their own files for TextIte
|
| +public: |
| + virtual ~GranularityStrategy() { }; |
| + virtual SelectionStrategy GetType() const; |
|
yosin_UTC9
2015/04/17 04:24:50
As of link error, we want to put "= 0".
mfomitchev
2015/04/17 14:41:49
Done.
|
| + virtual void Clear(); |
|
yosin_UTC9
2015/04/17 04:24:50
ditto
mfomitchev
2015/04/17 14:41:49
Done.
|
| + |
| + // Calculates and returns the new selection based on the updated user selection extent |extentPosition| and the granularity strategy. |
| + virtual VisibleSelection updateExtent(const VisiblePosition& extentPosition, const VisibleSelection&); |
|
yosin_UTC9
2015/04/17 04:24:50
ditto
mfomitchev
2015/04/17 14:41:49
Done.
|
| +protected: |
| + GranularityStrategy() { }; |
| +}; |
| + |
| +// Always uses character granularity. |
| +class CharacterGranularityStrategy : public GranularityStrategy { |
|
yosin_UTC9
2015/04/17 04:24:50
nit: "final"
mfomitchev
2015/04/17 14:41:49
Done.
|
| +public: |
| + CharacterGranularityStrategy() { }; |
| + ~CharacterGranularityStrategy() override { }; |
| + |
| + SelectionStrategy GetType() const override { return StrategyCharacter; } |
| + void Clear() override { }; |
| + VisibleSelection updateExtent(const VisiblePosition& extentPosition, const VisibleSelection& selection) override |
| + { |
| + return VisibleSelection(selection.visibleBase(), extentPosition); |
| + }; |
| +}; |
| + |
| +// "Expand by word, shrink by character" selection strategy. |
| +// Uses character granularity when selection is shrinking. If the selection is expanding, |
| +// granularity doesn't change until a word boundary is passed, after which the granularity |
| +// switches to "word". |
| +class DirectionGranularityStrategy : public GranularityStrategy { |
|
yosin_UTC9
2015/04/17 04:24:50
nit: final
mfomitchev
2015/04/17 14:41:49
Done.
|
| +public: |
| + DirectionGranularityStrategy(); |
| + ~DirectionGranularityStrategy() override { }; |
| + |
| + SelectionStrategy GetType() const override { return StrategyDirection; } |
| + void Clear() override; |
| + VisibleSelection updateExtent(const VisiblePosition&, const VisibleSelection&) override; |
| +private: |
| + enum EWordBoundAdjust {CurrentPosIfOnBound = false, NextBoundIfOnBound = true}; |
|
yosin_UTC9
2015/04/17 04:24:51
nit: we don't need to assign value for enum fields
yosin_UTC9
2015/04/17 04:24:51
nit: better to use |enum class|.
mfomitchev
2015/04/17 14:41:49
Done.
mfomitchev
2015/04/17 14:41:49
Neat! Thanks for the tip!
|
| + enum ESearchDirection {SearchBackwards = 0, SearchForward = 1}; |
|
yosin_UTC9
2015/04/17 04:24:50
nit: we don't need to assign value for enum fields
yosin_UTC9
2015/04/17 04:24:50
nit: better to use |enum class|.
mfomitchev
2015/04/17 14:41:49
Done.
mfomitchev
2015/04/17 14:41:49
Done.
|
| + |
| + // Returns the next word boundary starting from |pos|. |direction| specifies the direction |
| + // in which to search for the next bound. nextIfOnBound controls whether |pos| or the next boundary |
| + // is returned when |pos| is located exactly on word boundary. |
| + VisiblePosition nextWordBound(const VisiblePosition& /*pos*/, ESearchDirection /*direction*/, EWordBoundAdjust /*nextIfOnBound*/); |
| + |
| + // Current selection granularity being used |
| + TextGranularity m_granularity; |
| + // Set to true if the selection was shrunk (without changing relative base/extent order) |
| + // as a result of the most recent updateExtent call. |
| + bool m_lastMoveShrunkSelection; |
| +}; |
| + |
| +DirectionGranularityStrategy::DirectionGranularityStrategy() |
| + : m_granularity(CharacterGranularity) |
| + , m_lastMoveShrunkSelection(false) { } |
| + |
| +void DirectionGranularityStrategy::Clear() |
| +{ |
| + m_granularity = CharacterGranularity; |
| + m_lastMoveShrunkSelection = false; |
| +} |
| + |
| +VisiblePosition DirectionGranularityStrategy::nextWordBound( |
| + const VisiblePosition& pos, |
| + ESearchDirection direction, |
| + EWordBoundAdjust wordBoundAdjust) |
| +{ |
| + if (direction == SearchForward) |
| + return endOfWord(pos, wordBoundAdjust == CurrentPosIfOnBound ? LeftWordIfOnBoundary : RightWordIfOnBoundary); |
| + return startOfWord(pos, wordBoundAdjust == CurrentPosIfOnBound ? RightWordIfOnBoundary : LeftWordIfOnBoundary); |
| +} |
| + |
| +VisibleSelection DirectionGranularityStrategy::updateExtent(const VisiblePosition& extentPosition, const VisibleSelection& selection) |
| +{ |
| + if (extentPosition == selection.visibleExtent()) |
| + return selection; |
| + |
| + const VisiblePosition base = selection.visibleBase(); |
| + const VisiblePosition oldExtentWithGranularity = selection.isBaseFirst() ? selection.visibleEnd() : selection.visibleStart(); |
| + |
| + int extentBaseOrder = comparePositions(extentPosition, base); |
| + int oldExtentBaseOrder = comparePositions(oldExtentWithGranularity, base); |
| + |
| + bool extentBaseOrderSwitched = (extentBaseOrder > 0 && oldExtentBaseOrder < 0) |
| + || (extentBaseOrder < 0 && oldExtentBaseOrder > 0); |
| + |
| + // Determine the boundary of the 'current word', i.e. the boundary extending beyond which |
| + // should change the granularity to WordGranularity. |
| + // If the last move has shrunk the selection and is now exactly on the word boundary - |
| + // we need to take the next bound as the bound of the "current word". |
| + VisiblePosition currentWordBoundary = nextWordBound( |
| + oldExtentWithGranularity, |
| + oldExtentBaseOrder > 0 ? SearchForward : SearchBackwards, |
| + m_lastMoveShrunkSelection ? NextBoundIfOnBound : CurrentPosIfOnBound); |
| + |
| + bool thisMoveShrunkSelection = (extentBaseOrder > 0 && comparePositions(extentPosition, selection.visibleExtent()) < 0) |
| + || (extentBaseOrder < 0 && comparePositions(extentPosition, selection.visibleExtent()) > 0); |
| + // If the extent-base order was switched, then the selection is now expanding in a different |
| + // direction than before. Therefore we need to calculate the boundary of the 'current word' |
| + // in this new direction in order to be able to tell if the selection expanded beyond it. |
| + if (extentBaseOrderSwitched) { |
| + currentWordBoundary = nextWordBound(base, extentBaseOrder > 0 ? SearchForward : SearchBackwards, NextBoundIfOnBound); |
| + m_granularity = CharacterGranularity; |
| + // When the base/extent order switches it doesn't count as shrinking selection. |
| + thisMoveShrunkSelection = false; |
| + } |
| + |
| + bool expandedBeyondWordBoundary; |
| + if (extentBaseOrder > 0) |
| + expandedBeyondWordBoundary = comparePositions(extentPosition, currentWordBoundary) > 0; |
| + else |
| + expandedBeyondWordBoundary = comparePositions(extentPosition, currentWordBoundary) < 0; |
| + if (expandedBeyondWordBoundary) { |
| + m_granularity = WordGranularity; |
| + } else if (thisMoveShrunkSelection) { |
| + m_granularity = CharacterGranularity; |
| + m_lastMoveShrunkSelection = true; |
| + } |
| + |
| + m_lastMoveShrunkSelection = thisMoveShrunkSelection; |
| + VisibleSelection newSelection = selection; |
| + newSelection.setExtent(extentPosition); |
| + if (m_granularity == WordGranularity) { |
| + if (extentBaseOrder > 0) |
| + newSelection.setEndRespectingGranularity(m_granularity, LeftWordIfOnBoundary); |
| + else |
| + newSelection.setStartRespectingGranularity(m_granularity, RightWordIfOnBoundary); |
| + } |
| + |
| + return newSelection; |
| +} |
| + |
| FrameSelection::FrameSelection(LocalFrame* frame) |
| : m_frame(frame) |
| , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation()) |
| @@ -222,11 +356,15 @@ void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& |
| void FrameSelection::setSelection(const VisibleSelection& newSelection, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity) |
| { |
| + if (m_granularityStrategy && (options & FrameSelection::DoNotClearStrategy) == 0) { |
| + m_granularityStrategy->Clear(); |
| + } |
| bool closeTyping = options & CloseTyping; |
| bool shouldClearTypingStyle = options & ClearTypingStyle; |
| EUserTriggered userTriggered = selectionOptionsToUserTriggered(options); |
| VisibleSelection s = validateSelection(newSelection); |
| + |
| if (shouldAlwaysUseDirectionalSelection(m_frame)) |
| s.setIsDirectional(true); |
| @@ -1173,6 +1311,8 @@ LayoutUnit FrameSelection::lineDirectionPointForBlockDirectionNavigation(EPositi |
| void FrameSelection::clear() |
| { |
| m_granularity = CharacterGranularity; |
| + if (m_granularityStrategy) |
| + m_granularityStrategy->Clear(); |
| setSelection(VisibleSelection()); |
| } |
| @@ -1913,21 +2053,38 @@ bool FrameSelection::selectWordAroundPosition(const VisiblePosition& position) |
| return false; |
| } |
| -void FrameSelection::moveRangeSelectionExtent(const VisiblePosition& extentPosition, TextGranularity granularity) |
| +GranularityStrategy* FrameSelection::granularityStrategy() |
| { |
| - if (isNone()) |
| - return; |
| + // We do lazy initalization for m_granularityStrategy, because if we initialize it |
| + // right in the constructor - the correct settings may not be set yet. |
| + SelectionStrategy strategyType = StrategyCharacter; |
| + Settings* settings = m_frame ? m_frame->settings() : 0; |
| + if (settings && settings->selectionStrategy() == StrategyDirection) |
| + strategyType = StrategyDirection; |
| - const VisiblePosition basePosition = m_selection.isBaseFirst() ? m_selection.visibleStart() : m_selection.visibleEnd(); |
| - VisibleSelection newSelection(basePosition, extentPosition); |
| - if (newSelection.isBaseFirst()) |
| - newSelection.setEndRespectingGranularity(granularity); |
| + if (m_granularityStrategy && m_granularityStrategy->GetType() == strategyType) |
| + return m_granularityStrategy.get(); |
| + |
| + if (strategyType == StrategyDirection) |
| + m_granularityStrategy = adoptPtr(new DirectionGranularityStrategy()); |
| else |
| - newSelection.setStartRespectingGranularity(granularity); |
| - if (!newSelection.isRange()) |
| + m_granularityStrategy = adoptPtr(new CharacterGranularityStrategy()); |
| + return m_granularityStrategy.get(); |
| +} |
| + |
| +void FrameSelection::moveRangeSelectionExtent(const VisiblePosition& extentPosition) |
| +{ |
| + const VisiblePosition base = m_selection.visibleBase(); |
| + |
| + if (isNone() || base == extentPosition) |
| return; |
| - setSelection(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | UserTriggered, FrameSelection::AlignCursorOnScrollIfNeeded, granularity); |
| + VisibleSelection newSelection = granularityStrategy()->updateExtent(extentPosition, selection()); |
| + setSelection( |
| + newSelection, |
| + FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::DoNotClearStrategy | UserTriggered, |
| + FrameSelection::AlignCursorOnScrollIfNeeded, |
| + CharacterGranularity); |
| } |
| void FrameSelection::moveRangeSelection(const VisiblePosition& basePosition, const VisiblePosition& extentPosition, TextGranularity granularity) |