Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: Source/core/editing/GranularityStrategy.cpp

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

Powered by Google App Engine
This is Rietveld 408576698