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

Side by Side Diff: third_party/WebKit/Source/core/editing/TextSuggestionController.cpp

Issue 2931443003: Add support for Android spellcheck menu in Chrome/WebViews (Closed)
Patch Set: Created 3 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
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/editing/TextSuggestionController.h"
6
7 #include "core/editing/Editor.h"
8 #include "core/editing/FrameSelection.h"
9 #include "core/editing/PlainTextRange.h"
10 #include "core/editing/markers/DocumentMarkerController.h"
11 #include "core/editing/markers/SpellCheckMarker.h"
12 #include "core/editing/spellcheck/SpellChecker.h"
13 #include "core/frame/FrameView.h"
14 #include "core/frame/LocalFrame.h"
15 #include "public/platform/InterfaceProvider.h"
16
17 namespace blink {
18
19 namespace {
20
21 const double kSuggestionUnderlineAlphaMultiplier = 0.4;
22
23 } // anonymous namespace
24
25 TextSuggestionController::TextSuggestionController(LocalFrame& frame)
26 : suggestion_menu_is_open_(false), frame_(&frame) {}
27
28 bool TextSuggestionController::IsMenuOpen() const {
29 return suggestion_menu_is_open_;
30 }
31
32 void TextSuggestionController::HandlePotentialMisspelledWordTap() {
33 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
34 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
35 if (!node_and_marker)
36 return;
37
38 if (!text_suggestion_host_) {
39 GetFrame().GetInterfaceProvider()->GetInterface(
40 mojo::MakeRequest(&text_suggestion_host_));
41 }
42
43 text_suggestion_host_->StartSpellCheckMenuTimer();
44 }
45
46 DEFINE_TRACE(TextSuggestionController) {
47 visitor->Trace(frame_);
48 }
49
50 void TextSuggestionController::ApplySpellCheckSuggestion(
51 const String& suggestion) {
52 GetFrame().GetSpellChecker().ReplaceMisspelledRange(suggestion);
53 SuggestionMenuClosed();
54 }
55
56 void TextSuggestionController::DeleteActiveSuggestionRange() {
57 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
58 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
59 if (!node_and_marker)
60 return;
61
62 Node* marker_text_node = node_and_marker.value().first;
63 DocumentMarker* marker = node_and_marker.value().second;
64
65 // If the character immediately following the range to be deleted is a space,
66 // delete it if either of these conditions holds:
67 // - We're deleting at the beginning of the editable text (to avoid ending up
68 // with a space at the beginning)
69 // - The character immediately before the range being deleted is also a space
70 // (to avoid ending up with two adjacent spaces)
71 bool delete_next_char = false;
yosin_UTC9 2017/06/07 01:34:15 Could you move calculation of |delete_next_char| i
72 const EphemeralRange next_character_range =
73 PlainTextRange(marker->EndOffset(), marker->EndOffset() + 1)
74 .CreateRange(*marker_text_node->parentNode());
75 if (next_character_range.IsNotNull()) {
76 String next_character_str = PlainText(
77 next_character_range,
78 TextIteratorBehavior::Builder().SetEmitsSpaceForNbsp(true).Build());
79
80 if (WTF::IsASCIISpace(next_character_str[0])) {
81 if (marker->StartOffset() == 0) {
82 delete_next_char = true;
83 } else {
84 const EphemeralRange prev_character_range =
85 PlainTextRange(marker->StartOffset() - 1, marker->StartOffset())
86 .CreateRange(*marker_text_node->parentNode());
87 if (prev_character_range.IsNotNull()) {
88 String prev_character_str =
89 PlainText(prev_character_range, TextIteratorBehavior::Builder()
90 .SetEmitsSpaceForNbsp(true)
91 .Build());
92 if (WTF::IsASCIISpace(prev_character_str[0]))
93 delete_next_char = true;
94 }
95 }
96 }
97 }
98
99 EphemeralRange range_to_delete = EphemeralRange(
yosin_UTC9 2017/06/07 01:34:15 nit: s/EphemeralRange/const EphemeralRange/
100 Position(marker_text_node, marker->StartOffset()),
101 Position(marker_text_node, marker->EndOffset() + delete_next_char));
yosin_UTC9 2017/06/07 01:34:15 At first glance "int + bool" is strange, but study
102
103 GetFrame().Selection().SetSelection(
104 SelectionInDOMTree::Builder().SetBaseAndExtent(range_to_delete).Build());
105
106 GetFrame().GetEditor().ReplaceSelectionWithText(
107 "", false, false, InputEvent::InputType::kDeleteByComposition);
108
109 SuggestionMenuClosed();
110 }
111
112 void TextSuggestionController::NewWordAddedToDictionary(const String& word) {
113 // Android pops up a dialog to let the user confirm they actually want to add
114 // the word to the dictionary; this method gets called as soon as the dialog
115 // is shown. So the word isn't actually in the dictionary here, even if the
116 // user will end up confirming the dialog, and we shouldn't try to re-run
117 // spellcheck here.
118
119 // Note: this actually matches the behavior in native Android text boxes
120 GetDocument().Markers().RemoveSpellingMarkersUnderWords(
121 Vector<String>({word}));
122 SuggestionMenuClosed();
123 }
124
125 void TextSuggestionController::SpellCheckMenuTimeoutCallback() {
126 Optional<std::pair<Node*, SpellCheckMarker*>> const node_and_marker =
127 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
128 if (!node_and_marker)
129 return;
130
131 Node* const container_node = node_and_marker.value().first;
132 SpellCheckMarker* const marker = node_and_marker.value().second;
133
134 Range* marker_range =
135 Range::Create(GetDocument(), container_node, marker->StartOffset(),
136 container_node, marker->EndOffset());
137
138 const String& misspelled_word = marker_range->GetText();
139 const String& description = marker->Description();
140
141 suggestion_menu_is_open_ = true;
142 GetFrame().Selection().SetCaretVisible(false);
143 GetDocument().Markers().AddActiveSuggestionMarker(
144 EphemeralRange(marker_range), SK_ColorTRANSPARENT,
145 StyleableMarker::Thickness::kThin,
146 Color(SK_ColorRED).CombineWithAlpha(kSuggestionUnderlineAlphaMultiplier));
147
148 Vector<String> suggestions;
149 description.Split('\n', suggestions);
150
151 Vector<mojom::blink::SpellCheckSuggestionPtr> suggestion_ptrs;
152 for (const String& suggestion : suggestions) {
153 mojom::blink::SpellCheckSuggestionPtr info_ptr(
154 mojom::blink::SpellCheckSuggestion::New());
155 info_ptr->suggestion = suggestion;
156 suggestion_ptrs.push_back(std::move(info_ptr));
157 }
158
159 const IntRect& absolute_bounds = GetFrame().Selection().AbsoluteCaretBounds();
160 const IntRect& viewport_bounds =
161 GetFrame().View()->ContentsToViewport(absolute_bounds);
162
163 text_suggestion_host_->ShowSpellCheckSuggestionMenu(
164 viewport_bounds.X(), viewport_bounds.MaxY(), std::move(misspelled_word),
165 std::move(suggestion_ptrs));
166 }
167
168 void TextSuggestionController::SuggestionMenuClosed() {
169 GetDocument().Markers().RemoveMarkersOfTypes(
170 DocumentMarker::kActiveSuggestion);
171 GetFrame().Selection().SetCaretVisible(true);
172 suggestion_menu_is_open_ = false;
173 }
174
175 Document& TextSuggestionController::GetDocument() const {
176 DCHECK(IsAvailable());
177 return *GetFrame().GetDocument();
178 }
179
180 bool TextSuggestionController::IsAvailable() const {
181 return GetFrame().GetDocument();
182 }
183
184 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698