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) |