| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "config.h" | 5 #include "config.h" |
| 6 #include "core/editing/GranularityStrategy.h" | 6 #include "core/editing/GranularityStrategy.h" |
| 7 | 7 |
| 8 #include "core/editing/FrameSelection.h" |
| 8 #include "core/editing/htmlediting.h" | 9 #include "core/editing/htmlediting.h" |
| 9 | 10 |
| 10 namespace blink { | 11 namespace blink { |
| 11 | 12 |
| 13 enum class BoundAdjust {CurrentPosIfOnBound, NextBoundIfOnBound}; |
| 14 enum class SearchDirection {SearchBackwards, SearchForward}; |
| 15 |
| 16 // We use the bottom-left corner of the caret rect to represent the |
| 17 // location of a VisiblePosition. This way locations corresponding to |
| 18 // VisiblePositions on the same line will all have the same y coordinate |
| 19 // unless the text is transformed. |
| 20 static IntPoint positionLocation(const VisiblePosition& vp) |
| 21 { |
| 22 return vp.absoluteCaretBounds().minXMaxYCorner(); |
| 23 } |
| 24 |
| 25 // Order is specified using the same contract as comparePositions. |
| 26 static bool arePositionsInSpecifiedOrder( |
| 27 const VisiblePosition& vp1, |
| 28 const VisiblePosition& vp2, |
| 29 int specifiedOrder) |
| 30 { |
| 31 int positionOrder = comparePositions(vp1, vp2); |
| 32 if (specifiedOrder == 0) |
| 33 return positionOrder == 0; |
| 34 return specifiedOrder > 0 ? positionOrder > 0 : positionOrder < 0; |
| 35 } |
| 36 |
| 37 // Returns the next word boundary starting from |pos|. |direction| specifies |
| 38 // the direction in which to search for the next bound. nextIfOnBound |
| 39 // controls whether |pos| or the next boundary is returned when |pos| is |
| 40 // located exactly on word boundary. |
| 41 static VisiblePosition nextWordBound( |
| 42 const VisiblePosition& pos, |
| 43 SearchDirection direction, |
| 44 BoundAdjust wordBoundAdjust) |
| 45 { |
| 46 bool nextBoundIfOnBound = wordBoundAdjust == BoundAdjust::NextBoundIfOnBoun
d; |
| 47 if (direction == SearchDirection::SearchForward) { |
| 48 EWordSide wordSide = nextBoundIfOnBound ? RightWordIfOnBoundary : LeftWo
rdIfOnBoundary; |
| 49 return endOfWord(pos, wordSide); |
| 50 } |
| 51 EWordSide wordSide = nextBoundIfOnBound ? LeftWordIfOnBoundary : RightWordIf
OnBoundary; |
| 52 return startOfWord(pos, wordSide); |
| 53 } |
| 54 |
| 12 GranularityStrategy::GranularityStrategy() { } | 55 GranularityStrategy::GranularityStrategy() { } |
| 13 | 56 |
| 14 GranularityStrategy::~GranularityStrategy() { } | 57 GranularityStrategy::~GranularityStrategy() { } |
| 15 | 58 |
| 16 CharacterGranularityStrategy::CharacterGranularityStrategy() { } | 59 CharacterGranularityStrategy::CharacterGranularityStrategy() { } |
| 17 | 60 |
| 18 CharacterGranularityStrategy::~CharacterGranularityStrategy() { } | 61 CharacterGranularityStrategy::~CharacterGranularityStrategy() { } |
| 19 | 62 |
| 20 SelectionStrategy CharacterGranularityStrategy::GetType() const | 63 SelectionStrategy CharacterGranularityStrategy::GetType() const |
| 21 { | 64 { |
| 22 return SelectionStrategy::Character; | 65 return SelectionStrategy::Character; |
| 23 } | 66 } |
| 24 | 67 |
| 25 void CharacterGranularityStrategy::Clear() { }; | 68 void CharacterGranularityStrategy::Clear() { }; |
| 26 | 69 |
| 27 VisibleSelection CharacterGranularityStrategy::updateExtent(const VisiblePositio
n& extentPosition, const VisibleSelection& selection) | 70 VisibleSelection CharacterGranularityStrategy::updateExtent(const IntPoint& exte
ntPoint, LocalFrame* frame) |
| 28 { | 71 { |
| 72 const VisiblePosition& extentPosition = visiblePositionForContentsPoint(exte
ntPoint, frame); |
| 73 const VisibleSelection& selection = frame->selection().selection(); |
| 74 if (selection.visibleBase() == extentPosition) |
| 75 return selection; |
| 29 return VisibleSelection(selection.visibleBase(), extentPosition); | 76 return VisibleSelection(selection.visibleBase(), extentPosition); |
| 30 } | 77 } |
| 31 | 78 |
| 32 DirectionGranularityStrategy::DirectionGranularityStrategy() | 79 DirectionGranularityStrategy::DirectionGranularityStrategy() |
| 33 : m_granularity(CharacterGranularity) | 80 : m_state(StrategyState::Cleared) |
| 34 , m_lastMoveShrunkSelection(false) { } | 81 , m_granularity(CharacterGranularity) |
| 82 , m_offset(0) { } |
| 35 | 83 |
| 36 DirectionGranularityStrategy::~DirectionGranularityStrategy() { } | 84 DirectionGranularityStrategy::~DirectionGranularityStrategy() { } |
| 37 | 85 |
| 38 SelectionStrategy DirectionGranularityStrategy::GetType() const | 86 SelectionStrategy DirectionGranularityStrategy::GetType() const |
| 39 { | 87 { |
| 40 return SelectionStrategy::Direction; | 88 return SelectionStrategy::Direction; |
| 41 } | 89 } |
| 42 | 90 |
| 43 void DirectionGranularityStrategy::Clear() | 91 void DirectionGranularityStrategy::Clear() |
| 44 { | 92 { |
| 93 m_state = StrategyState::Cleared; |
| 45 m_granularity = CharacterGranularity; | 94 m_granularity = CharacterGranularity; |
| 46 m_lastMoveShrunkSelection = false; | 95 m_offset = 0; |
| 96 m_diffExtentPointFromExtentPosition = IntSize(); |
| 47 } | 97 } |
| 48 | 98 |
| 49 VisiblePosition DirectionGranularityStrategy::nextWordBound( | 99 VisibleSelection DirectionGranularityStrategy::updateExtent(const IntPoint& exte
ntPoint, LocalFrame* frame) |
| 50 const VisiblePosition& pos, | |
| 51 SearchDirection direction, | |
| 52 BoundAdjust wordBoundAdjust) | |
| 53 { | 100 { |
| 54 bool nextBoundIfOnBound = wordBoundAdjust == BoundAdjust::NextBoundIfOnBoun
d; | 101 const VisibleSelection& selection = frame->selection().selection(); |
| 55 if (direction == SearchDirection::SearchForward) { | 102 |
| 56 EWordSide wordSide = nextBoundIfOnBound ? RightWordIfOnBoundary : LeftWo
rdIfOnBoundary; | 103 if (m_state == StrategyState::Cleared) |
| 57 return endOfWord(pos, wordSide); | 104 m_state = StrategyState::Expanding; |
| 105 |
| 106 VisiblePosition oldOffsetExtentPosition = selection.visibleExtent(); |
| 107 IntPoint oldExtentLocation = positionLocation(oldOffsetExtentPosition); |
| 108 |
| 109 IntPoint oldOffsetExtentPoint = oldExtentLocation + m_diffExtentPointFromExt
entPosition; |
| 110 IntPoint oldExtentPoint = IntPoint(oldOffsetExtentPoint.x() - m_offset, oldO
ffsetExtentPoint.y()); |
| 111 |
| 112 // Apply the offset. |
| 113 IntPoint newOffsetExtentPoint = extentPoint; |
| 114 int dx = extentPoint.x() - oldExtentPoint.x(); |
| 115 if (m_offset != 0) { |
| 116 if (m_offset > 0 && dx > 0) |
| 117 m_offset = std::max(0, m_offset - dx); |
| 118 else if (m_offset < 0 && dx < 0) |
| 119 m_offset = std::min(0, m_offset - dx); |
| 120 newOffsetExtentPoint.move(m_offset, 0); |
| 58 } | 121 } |
| 59 EWordSide wordSide = nextBoundIfOnBound ? LeftWordIfOnBoundary : RightWordIf
OnBoundary; | |
| 60 return startOfWord(pos, wordSide); | |
| 61 } | |
| 62 | 122 |
| 63 VisibleSelection DirectionGranularityStrategy::updateExtent(const VisiblePositio
n& extentPosition, const VisibleSelection& selection) | 123 VisiblePosition newOffsetExtentPosition = visiblePositionForContentsPoint(ne
wOffsetExtentPoint, frame); |
| 64 { | 124 IntPoint newOffsetLocation = positionLocation(newOffsetExtentPosition); |
| 65 if (extentPosition == selection.visibleExtent()) | 125 |
| 126 // Reset the offset in case of a vertical change in the location (could be |
| 127 // due to a line change or due to an unusual layout, e.g. rotated text). |
| 128 bool verticalChange = newOffsetLocation.y() != oldExtentLocation.y(); |
| 129 if (verticalChange) { |
| 130 m_offset = 0; |
| 131 m_granularity = CharacterGranularity; |
| 132 newOffsetExtentPoint = extentPoint; |
| 133 newOffsetExtentPosition = visiblePositionForContentsPoint(extentPoint, f
rame); |
| 134 } |
| 135 |
| 136 const VisiblePosition base = selection.visibleBase(); |
| 137 |
| 138 // Do not allow empty selection. |
| 139 if (newOffsetExtentPosition == base) |
| 66 return selection; | 140 return selection; |
| 67 | 141 |
| 68 const VisiblePosition base = selection.visibleBase(); | 142 // The direction granularity strategy, particularly the "offset" feature |
| 69 const VisiblePosition oldExtentWithGranularity = selection.isBaseFirst() ? s
election.visibleEnd() : selection.visibleStart(); | 143 // doesn't work with non-horizontal text (e.g. when the text is rotated). |
| 144 // So revert to the behavior equivalent to the character granularity |
| 145 // strategy if we detect that the text's baseline coordinate changed |
| 146 // without a line change. |
| 147 if (verticalChange && inSameLine(newOffsetExtentPosition, oldOffsetExtentPos
ition)) |
| 148 return VisibleSelection(selection.visibleBase(), newOffsetExtentPosition
); |
| 70 | 149 |
| 71 int extentBaseOrder = comparePositions(extentPosition, base); | 150 int oldExtentBaseOrder = selection.isBaseFirst() ? 1 : -1; |
| 72 int oldExtentBaseOrder = comparePositions(oldExtentWithGranularity, base); | |
| 73 | 151 |
| 74 bool extentBaseOrderSwitched = (extentBaseOrder > 0 && oldExtentBaseOrder <
0) | 152 int newExtentBaseOrder; |
| 75 || (extentBaseOrder < 0 && oldExtentBaseOrder > 0); | 153 bool thisMoveShrunkSelection; |
| 154 if (newOffsetExtentPosition == oldOffsetExtentPosition) { |
| 155 if (m_granularity == CharacterGranularity) |
| 156 return selection; |
| 76 | 157 |
| 77 // Determine the boundary of the 'current word', i.e. the boundary extending | 158 // If we are in Word granularity, we cannot exit here, since we may pass |
| 78 // beyond which should change the granularity to WordGranularity. | 159 // the middle of the word without changing the position (in which case |
| 79 // If the last move has shrunk the selection and is now exactly on the word | 160 // the selection needs to expand). |
| 80 // boundary - we need to take the next bound as the bound of the "current | 161 thisMoveShrunkSelection = false; |
| 81 // word". | 162 newExtentBaseOrder = oldExtentBaseOrder; |
| 82 VisiblePosition currentWordBoundary = nextWordBound( | 163 } else { |
| 83 oldExtentWithGranularity, | 164 bool selectionExpanded = arePositionsInSpecifiedOrder(newOffsetExtentPos
ition, oldOffsetExtentPosition, oldExtentBaseOrder); |
| 84 oldExtentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirectio
n::SearchBackwards, | 165 bool extentBaseOrderSwitched = selectionExpanded ? false : !arePositions
InSpecifiedOrder(newOffsetExtentPosition, base, oldExtentBaseOrder); |
| 85 m_lastMoveShrunkSelection ? BoundAdjust::NextBoundIfOnBound : BoundAdjus
t::CurrentPosIfOnBound); | 166 newExtentBaseOrder = extentBaseOrderSwitched ? -oldExtentBaseOrder : old
ExtentBaseOrder; |
| 86 | 167 |
| 87 bool thisMoveShrunkSelection = (extentBaseOrder > 0 && comparePositions(exte
ntPosition, selection.visibleExtent()) < 0) | 168 // Determine the word boundary, i.e. the boundary extending beyond which |
| 88 || (extentBaseOrder < 0 && comparePositions(extentPosition, selection.vi
sibleExtent()) > 0); | 169 // should change the granularity to WordGranularity. |
| 89 // If the extent-base order was switched, then the selection is now | 170 VisiblePosition wordBoundary; |
| 90 // expanding in a different direction than before. Therefore we need to | 171 if (extentBaseOrderSwitched) { |
| 91 // calculate the boundary of the 'current word' in this new direction in | 172 // Special case. |
| 92 // order to be able to tell if the selection expanded beyond it. | 173 // If the extent-base order was switched, then the selection is now |
| 93 if (extentBaseOrderSwitched) { | 174 // expanding in a different direction than before. Therefore we |
| 94 currentWordBoundary = nextWordBound( | 175 // calculate the word boundary in this new direction and based on |
| 95 base, | 176 // the |base| position. |
| 96 extentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirecti
on::SearchBackwards, | 177 wordBoundary = nextWordBound( |
| 97 BoundAdjust::NextBoundIfOnBound); | 178 base, |
| 98 m_granularity = CharacterGranularity; | 179 newExtentBaseOrder > 0 ? SearchDirection::SearchForward : Search
Direction::SearchBackwards, |
| 99 // When the base/extent order switches it doesn't count as shrinking sel
ection. | 180 BoundAdjust::NextBoundIfOnBound); |
| 100 thisMoveShrunkSelection = false; | 181 m_granularity = CharacterGranularity; |
| 182 } else { |
| 183 // Calculate the word boundary based on |oldExtentWithGranularity|. |
| 184 // If selection was shrunk in the last update and the extent is now |
| 185 // exactly on the word boundary - we need to take the next bound as |
| 186 // the bound of the current word. |
| 187 wordBoundary = nextWordBound( |
| 188 oldOffsetExtentPosition, |
| 189 oldExtentBaseOrder > 0 ? SearchDirection::SearchForward : Search
Direction::SearchBackwards, |
| 190 m_state == StrategyState::Shrinking ? BoundAdjust::NextBoundIfOn
Bound : BoundAdjust::CurrentPosIfOnBound); |
| 191 } |
| 192 |
| 193 bool expandedBeyondWordBoundary; |
| 194 if (selectionExpanded) |
| 195 expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(newOffsetE
xtentPosition, wordBoundary, newExtentBaseOrder); |
| 196 else if (extentBaseOrderSwitched) |
| 197 expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(newOffsetE
xtentPosition, wordBoundary, newExtentBaseOrder); |
| 198 else |
| 199 expandedBeyondWordBoundary = false; |
| 200 |
| 201 // The selection is shrunk if the extent changes position to be closer t
o |
| 202 // the base, and the extent/base order wasn't switched. |
| 203 thisMoveShrunkSelection = !extentBaseOrderSwitched && !selectionExpanded
; |
| 204 |
| 205 if (expandedBeyondWordBoundary) |
| 206 m_granularity = WordGranularity; |
| 207 else if (thisMoveShrunkSelection) |
| 208 m_granularity = CharacterGranularity; |
| 101 } | 209 } |
| 102 | 210 |
| 103 bool expandedBeyondWordBoundary; | 211 VisiblePosition newSelectionExtent = newOffsetExtentPosition; |
| 104 if (extentBaseOrder > 0) | 212 if (m_granularity == WordGranularity) { |
| 105 expandedBeyondWordBoundary = comparePositions(extentPosition, currentWor
dBoundary) > 0; | 213 // Determine the bounds of the word where the extent is located. |
| 106 else | 214 // Set the selection extent to one of the two bounds depending on |
| 107 expandedBeyondWordBoundary = comparePositions(extentPosition, currentWor
dBoundary) < 0; | 215 // whether the extent is passed the middle of the word. |
| 108 if (expandedBeyondWordBoundary) { | 216 VisiblePosition boundBeforeExtent = nextWordBound(newOffsetExtentPositio
n, SearchDirection::SearchBackwards, BoundAdjust::CurrentPosIfOnBound); |
| 109 m_granularity = WordGranularity; | 217 VisiblePosition boundAfterExtent = nextWordBound(newOffsetExtentPosition
, SearchDirection::SearchForward, BoundAdjust::CurrentPosIfOnBound); |
| 110 } else if (thisMoveShrunkSelection) { | 218 int xMiddleBetweenBounds = (positionLocation(boundAfterExtent).x() + pos
itionLocation(boundBeforeExtent).x()) / 2; |
| 111 m_granularity = CharacterGranularity; | 219 bool offsetExtentBeforeMiddle = newOffsetExtentPoint.x() < xMiddleBetwee
nBounds; |
| 112 m_lastMoveShrunkSelection = true; | 220 newSelectionExtent = offsetExtentBeforeMiddle ? boundBeforeExtent : boun
dAfterExtent; |
| 221 // Update the offset if selection expanded in word granularity. |
| 222 if (newSelectionExtent != selection.visibleExtent() |
| 223 && ((newExtentBaseOrder > 0 && !offsetExtentBeforeMiddle) || (newExt
entBaseOrder < 0 && offsetExtentBeforeMiddle))) { |
| 224 m_offset = positionLocation(newSelectionExtent).x() - extentPoint.x(
); |
| 225 } |
| 113 } | 226 } |
| 114 | 227 |
| 115 m_lastMoveShrunkSelection = thisMoveShrunkSelection; | 228 // Only update the state if the selection actually changed as a result of |
| 229 // this move. |
| 230 if (newSelectionExtent != selection.visibleExtent()) |
| 231 m_state = thisMoveShrunkSelection ? StrategyState::Shrinking : StrategyS
tate::Expanding; |
| 232 |
| 233 m_diffExtentPointFromExtentPosition = extentPoint + IntSize(m_offset, 0) - p
ositionLocation(newSelectionExtent); |
| 116 VisibleSelection newSelection = selection; | 234 VisibleSelection newSelection = selection; |
| 117 newSelection.setExtent(extentPosition); | 235 newSelection.setExtent(newSelectionExtent); |
| 118 if (m_granularity == WordGranularity) { | |
| 119 if (extentBaseOrder > 0) | |
| 120 newSelection.setEndRespectingGranularity(m_granularity, LeftWordIfOn
Boundary); | |
| 121 else | |
| 122 newSelection.setStartRespectingGranularity(m_granularity, RightWordI
fOnBoundary); | |
| 123 } | |
| 124 | |
| 125 return newSelection; | 236 return newSelection; |
| 126 } | 237 } |
| 127 | 238 |
| 128 } // namespace blink | 239 } // namespace blink |
| OLD | NEW |