initial import
[vuplus_webkit] / Source / WebCore / editing / SpellingCorrectionController.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "SpellingCorrectionController.h"
29
30 #include "DocumentMarkerController.h"
31 #include "EditCommand.h"
32 #include "EditorClient.h"
33 #include "FloatQuad.h"
34 #include "Frame.h"
35 #include "FrameView.h"
36 #include "Page.h"
37 #include "SpellingCorrectionCommand.h"
38 #include "TextCheckerClient.h"
39 #include "TextCheckingHelper.h"
40 #include "TextIterator.h"
41 #include "VisibleSelection.h"
42 #include "htmlediting.h"
43 #include "markup.h"
44 #include "visible_units.h"
45
46 namespace WebCore {
47
48 using namespace std;
49 using namespace WTF;
50
51 #if USE(AUTOCORRECTION_PANEL)
52
53 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
54 {
55     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
56     if (markerTypesForAutoCorrection.isEmpty()) {
57         markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
58         markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
59         markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
60         markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
61     }
62     return markerTypesForAutoCorrection;
63 }
64
65 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
66 {
67     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
68     if (markerTypesForReplacement.isEmpty()) {
69         markerTypesForReplacement.append(DocumentMarker::Replacement);
70         markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
71     }
72     return markerTypesForReplacement;
73 }
74
75 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers)
76 {
77     if (markers.isEmpty())
78         return true;
79
80     const String& description = markers[0]->description();
81     for (size_t i = 1; i < markers.size(); ++i) {
82         if (description != markers[i]->description())
83             return false;
84     }
85     return true;
86 }
87
88 SpellingCorrectionController::SpellingCorrectionController(Frame* frame)
89     : m_frame(frame)
90     , m_correctionPanelTimer(this, &SpellingCorrectionController::correctionPanelTimerFired)
91 {
92 }
93
94 SpellingCorrectionController::~SpellingCorrectionController()
95 {
96     dismiss(ReasonForDismissingCorrectionPanelIgnored);
97 }
98
99 void SpellingCorrectionController::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)
100 {
101     const double correctionPanelTimerInterval = 0.3;
102     if (!isAutomaticSpellingCorrectionEnabled())
103         return;
104
105     // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
106     if (type == CorrectionPanelInfo::PanelTypeCorrection)
107         m_correctionPanelInfo.rangeToBeReplaced.clear();
108     m_correctionPanelInfo.panelType = type;
109     m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
110 }
111
112 void SpellingCorrectionController::stopCorrectionPanelTimer()
113 {
114     m_correctionPanelTimer.stop();
115     m_correctionPanelInfo.rangeToBeReplaced.clear();
116 }
117
118 void SpellingCorrectionController::stopPendingCorrection(const VisibleSelection& oldSelection)
119 {
120     // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
121     VisibleSelection currentSelection(m_frame->selection()->selection());
122     if (currentSelection == oldSelection)
123         return;
124
125     stopCorrectionPanelTimer();
126     dismiss(ReasonForDismissingCorrectionPanelIgnored);
127 }
128
129 void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
130 {
131     // Apply pending autocorrection before next round of spell checking.
132     bool doApplyCorrection = true;
133     VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
134     VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
135     if (currentWord.visibleEnd() == startOfSelection) {
136         String wordText = plainText(currentWord.toNormalizedRange().get());
137         if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
138             doApplyCorrection = false;
139     }
140     if (doApplyCorrection)
141         handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted)); 
142     else
143         m_correctionPanelInfo.rangeToBeReplaced.clear();
144 }
145
146 bool SpellingCorrectionController::hasPendingCorrection() const
147 {
148     return m_correctionPanelInfo.rangeToBeReplaced;
149 }
150
151 bool SpellingCorrectionController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
152 {
153     return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
154 }
155
156 void SpellingCorrectionController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
157 {
158     FloatRect boundingBox = windowRectForRange(rangeToReplace.get());
159     if (boundingBox.isEmpty())
160         return;
161     m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get());
162     m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace;
163     m_correctionPanelInfo.replacementString = replacement;
164     m_correctionPanelInfo.isActive = true;
165     client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, replacement, Vector<String>());
166 }
167
168 void SpellingCorrectionController::handleCancelOperation()
169 {
170     if (!m_correctionPanelInfo.isActive)
171         return;
172     m_correctionPanelInfo.isActive = false;
173     dismiss(ReasonForDismissingCorrectionPanelCancelled);
174 }
175
176 void SpellingCorrectionController::dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing)
177 {
178     if (!m_correctionPanelInfo.isActive)
179         return;
180     m_correctionPanelInfo.isActive = false;
181     m_correctionPanelIsDismissedByEditor = true;
182     if (client())
183         client()->dismissCorrectionPanel(reasonForDismissing);
184 }
185
186 String SpellingCorrectionController::dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
187 {
188     if (!m_correctionPanelInfo.isActive)
189         return String();
190     m_correctionPanelInfo.isActive = false;
191     m_correctionPanelIsDismissedByEditor = true;
192     if (!client())
193         return String();
194     return client()->dismissCorrectionPanelSoon(reasonForDismissing);
195 }
196
197 void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
198 {
199     if (!m_correctionPanelInfo.rangeToBeReplaced)
200         return;
201
202     ExceptionCode ec = 0;
203     RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
204     if (ec)
205         return;
206
207     setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition()));
208     setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition()));
209
210     // After we replace the word at range rangeToBeReplaced, we need to add markers to that range.
211     // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore.
212     // So before we carry out the replacement, we need to store the start position of rangeToBeReplaced
213     // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph
214     // to store this value. In order to obtain this offset, we need to first create a range
215     // which spans from the start of paragraph to the start position of rangeToBeReplaced.
216     RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
217     if (ec)
218         return;
219
220     Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->startPosition();
221     correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec);
222     if (ec)
223         return;
224
225     // Take note of the location of autocorrection so that we can add marker after the replacement took place.
226     int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get());
227
228     // Clone the range, since the caller of this method may want to keep the original range around.
229     RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
230     applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString));
231     setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
232     RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph,  m_correctionPanelInfo.replacementString.length());
233     String newText = plainText(replacementRange.get());
234
235     // Check to see if replacement succeeded.
236     if (newText != m_correctionPanelInfo.replacementString)
237         return;
238
239     DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
240     size_t size = markerTypesToAdd.size();
241     for (size_t i = 0; i < size; ++i) {
242         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
243         String description;
244         if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
245             description = m_correctionPanelInfo.replacedString;
246         markers->addMarker(replacementRange.get(), markerType, description);
247     }
248 }
249
250 bool SpellingCorrectionController::applyAutocorrectionBeforeTypingIfAppropriate()
251 {
252     if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive)
253         return false;
254
255     if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection)
256         return false;
257
258     Position caretPosition = m_frame->selection()->selection().start();
259
260     if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) {
261         handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
262         return true;
263     } 
264     
265     // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction.
266     ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition);
267     dismiss(ReasonForDismissingCorrectionPanelIgnored);
268     return false;
269 }
270
271 void SpellingCorrectionController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
272 {
273     client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
274     m_frame->document()->updateLayout();
275     m_frame->selection()->setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered);
276     RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
277
278     DocumentMarkerController* markers = m_frame->document()->markers();
279     markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
280     markers->addMarker(range.get(), DocumentMarker::Replacement);
281     markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
282 }
283
284 void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorrectionController>*)
285 {
286     m_correctionPanelIsDismissedByEditor = false;
287     switch (m_correctionPanelInfo.panelType) {
288     case CorrectionPanelInfo::PanelTypeCorrection: {
289         VisibleSelection selection(m_frame->selection()->selection());
290         VisiblePosition start(selection.start(), selection.affinity());
291         VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
292         VisibleSelection adjacentWords = VisibleSelection(p, start);
293         m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
294     }
295         break;
296     case CorrectionPanelInfo::PanelTypeReversion: {
297         if (!m_correctionPanelInfo.rangeToBeReplaced)
298             break;
299         m_correctionPanelInfo.isActive = true;
300         m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
301         FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
302         if (!boundingBox.isEmpty())
303             client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>());
304     }
305         break;
306     case CorrectionPanelInfo::PanelTypeSpellingSuggestions: {
307         if (!m_correctionPanelInfo.rangeToBeReplaced || plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString)
308             break;
309         String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get());
310         Vector<String> suggestions;
311         textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions);
312         if (suggestions.isEmpty()) {
313             m_correctionPanelInfo.rangeToBeReplaced.clear();
314             break;
315         }
316         String topSuggestion = suggestions.first();
317         suggestions.remove(0);
318         m_correctionPanelInfo.isActive = true;
319         FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
320         if (!boundingBox.isEmpty())
321             client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions);
322     }
323         break;
324     }
325 }
326
327 void SpellingCorrectionController::handleCorrectionPanelResult(const String& correction)
328 {
329     Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get();
330     if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
331         return;
332
333     String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
334     // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
335     if (currentWord != m_correctionPanelInfo.replacedString)
336         return;
337
338     m_correctionPanelInfo.isActive = false;
339
340     switch (m_correctionPanelInfo.panelType) {
341     case CorrectionPanelInfo::PanelTypeCorrection:
342         if (correction.length()) {
343             m_correctionPanelInfo.replacementString = correction;
344             applyCorrectionPanelInfo(markerTypesForAutocorrection());
345         } else if (!m_correctionPanelIsDismissedByEditor)
346             replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString);
347         break;
348     case CorrectionPanelInfo::PanelTypeReversion:
349     case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
350         if (correction.length()) {
351             m_correctionPanelInfo.replacementString = correction;
352             applyCorrectionPanelInfo(markerTypesForReplacement());
353         }
354         break;
355     }
356
357     m_correctionPanelInfo.rangeToBeReplaced.clear();
358 }
359
360 bool SpellingCorrectionController::isAutomaticSpellingCorrectionEnabled()
361 {
362     return client() && client()->isAutomaticSpellingCorrectionEnabled();
363 }
364
365 FloatRect SpellingCorrectionController::windowRectForRange(const Range* range) const
366 {
367     FrameView* view = m_frame->view();
368     if (!view)
369         return FloatRect();
370     Vector<FloatQuad> textQuads;
371     range->textQuads(textQuads);
372     FloatRect boundingRect;
373     size_t size = textQuads.size();
374     for (size_t i = 0; i < size; ++i)
375         boundingRect.unite(textQuads[i].boundingBox());
376     return view->contentsToWindow(IntRect(boundingRect));
377 }        
378
379 void SpellingCorrectionController::respondToChangedSelection(const VisibleSelection& oldSelection)
380 {
381     VisibleSelection currentSelection(m_frame->selection()->selection());
382     // When user moves caret to the end of autocorrected word and pauses, we show the panel
383     // containing the original pre-correction word so that user can quickly revert the
384     // undesired autocorrection. Here, we start correction panel timer once we confirm that
385     // the new caret position is at the end of a word.
386     if (!currentSelection.isCaret() || currentSelection == oldSelection)
387         return;
388
389     VisiblePosition selectionPosition = currentSelection.start();
390     
391     // Creating a Visible position triggers a layout and there is no
392     // guarantee that the selection is still valid.
393     if (selectionPosition.isNull())
394         return;
395     
396     VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
397     if (selectionPosition != endPositionOfWord)
398         return;
399
400     Position position = endPositionOfWord.deepEquivalent();
401     if (position.anchorType() != Position::PositionIsOffsetInAnchor)
402         return;
403
404     Node* node = position.containerNode();
405     int endOffset = position.offsetInContainerNode();
406     Vector<DocumentMarker*> markers = node->document()->markers()->markersFor(node);
407     size_t markerCount = markers.size();
408     for (size_t i = 0; i < markerCount; ++i) {
409         const DocumentMarker* marker = markers[i];
410         if (!shouldStartTimerFor(marker, endOffset))
411             continue;
412         RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker->startOffset(), node, marker->endOffset());
413         String currentWord = plainText(wordRange.get());
414         if (!currentWord.length())
415             continue;
416
417         m_correctionPanelInfo.rangeToBeReplaced = wordRange;
418         m_correctionPanelInfo.replacedString = currentWord;
419         if (marker->type() == DocumentMarker::Spelling)
420             startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions);
421         else {
422             m_correctionPanelInfo.replacementString = marker->description();
423             startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion);
424         }
425
426         break;
427     }
428 }
429
430 void SpellingCorrectionController::respondToAppliedEditing(EditCommand* command)
431 {
432     if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
433         m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
434
435     markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command);
436     m_originalStringForLastDeletedAutocorrection = String();
437 }
438
439 void SpellingCorrectionController::respondToUnappliedEditing(EditCommand* command)
440 {
441     if (!command->isCreateLinkCommand())
442         return;
443     RefPtr<Range> range = Range::create(m_frame->document(), command->startingSelection().start(), command->startingSelection().end());
444     if (!range)
445         return;
446     DocumentMarkerController* markers = m_frame->document()->markers();
447     markers->addMarker(range.get(), DocumentMarker::Replacement);
448     markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
449 }
450
451 EditorClient* SpellingCorrectionController::client()
452 {
453     return m_frame->page() ? m_frame->page()->editorClient() : 0;
454 }
455
456 TextCheckerClient* SpellingCorrectionController::textChecker()
457 {
458     if (EditorClient* owner = client())
459         return owner->textChecker();
460     return 0;
461 }
462
463 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
464 {
465     client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacementString);
466 }
467
468 void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
469 {
470     recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
471 }
472
473 void SpellingCorrectionController::markReversed(PassRefPtr<Range> changedRange)
474 {
475     changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
476     changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
477 }
478
479 void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
480 {
481     Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
482     DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers();
483     for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
484         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
485         if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
486             markers->addMarker(replacedRange.get(), markerType, replacedString);
487         else
488             markers->addMarker(replacedRange.get(), markerType);
489     }
490 }
491
492 void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
493 {
494     if (!rangeOfCorrection)
495         return;
496     DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
497     Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
498     if (correctedOnceMarkers.isEmpty())
499         return;
500     
501     // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
502     // edited it to something else, and notify spellchecker accordingly.
503     if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected)
504         client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
505     else
506         client()->recordAutocorrectionResponse(EditorClient::AutocorrectionEdited, corrected, correction);
507     markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
508 }
509
510 void SpellingCorrectionController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
511 {
512     m_originalStringForLastDeletedAutocorrection = originalString;
513     m_positionForLastDeletedAutocorrection = position;
514 }
515
516 void SpellingCorrectionController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command)
517 {
518     Position endOfSelection = command->endingSelection().end();
519     if (endOfSelection != m_positionForLastDeletedAutocorrection)
520         return;
521
522     Position precedingCharacterPosition = endOfSelection.previous();
523     if (endOfSelection == precedingCharacterPosition)
524         return;
525
526     RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, endOfSelection);
527     String string = plainText(precedingCharacterRange.get());
528     if (string.isEmpty() || !isWhitespace(string[string.length() - 1]))
529         return;
530
531     // Mark this whitespace to indicate we have deleted an autocorrection following this
532     // whitespace. So if the user types the same original word again at this position, we
533     // won't autocorrect it again.
534     m_frame->document()->markers()->addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection);
535 }
536
537 bool SpellingCorrectionController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeToBeReplaced, const String& stringToBeReplaced)
538 {
539     DocumentMarkerController* markerController = m_frame->document()->markers();
540     if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::Replacement)) {
541         if (result->type == TextCheckingTypeCorrection)
542             recordSpellcheckerResponseForModifiedCorrection(rangeToBeReplaced, stringToBeReplaced, result->replacement);
543         return false;
544     }
545
546     if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::RejectedCorrection))
547         return false;
548
549     Position beginningOfRange = rangeToBeReplaced->startPosition();
550     Position precedingCharacterPosition = beginningOfRange.previous();
551     RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange);
552
553     Vector<DocumentMarker*> markers = markerController->markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection);
554
555     for (size_t i = 0; i < markers.size(); ++i) {
556         if (markers[i]->description() == stringToBeReplaced)
557             return false;
558     }
559
560     return true;
561 }
562     
563 #endif
564
565 } // namespace WebCore