[Mac] Text replacement should use correction indicator
[WebKit-https.git] / Source / WebCore / editing / AlternativeTextController.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 "AlternativeTextController.h"
29
30 #include "DictationAlternative.h"
31 #include "Document.h"
32 #include "DocumentMarkerController.h"
33 #include "EditCommand.h"
34 #include "Editor.h"
35 #include "EditorClient.h"
36 #include "Event.h"
37 #include "ExceptionCodePlaceholder.h"
38 #include "FloatQuad.h"
39 #include "Frame.h"
40 #include "FrameView.h"
41 #include "Page.h"
42 #include "SpellingCorrectionCommand.h"
43 #include "TextCheckerClient.h"
44 #include "TextCheckingHelper.h"
45 #include "TextEvent.h"
46 #include "TextIterator.h"
47 #include "VisibleSelection.h"
48 #include "VisibleUnits.h"
49 #include "htmlediting.h"
50 #include "markup.h"
51
52 namespace WebCore {
53
54 using namespace std;
55 using namespace WTF;
56
57 class AutocorrectionAlternativeDetails : public AlternativeTextDetails {
58 public:
59     static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString)
60     {
61         return adoptRef(new AutocorrectionAlternativeDetails(replacementString));
62     }
63     
64     const String& replacementString() const { return m_replacementString; }
65 private:
66     AutocorrectionAlternativeDetails(const String& replacementString)
67     : m_replacementString(replacementString)
68     { }
69     
70     String m_replacementString;
71 };
72
73 class DictationAlternativeDetails : public AlternativeTextDetails {
74 public:
75     static PassRefPtr<DictationAlternativeDetails> create(uint64_t dictationContext)
76     {
77         return adoptRef(new DictationAlternativeDetails(dictationContext));
78     }
79
80     uint64_t dictationContext() const { return m_dictationContext; }
81
82 private:
83     DictationAlternativeDetails(uint64_t dictationContext)
84     : m_dictationContext(dictationContext)
85     { }
86
87     uint64_t m_dictationContext;
88 };
89
90 #if USE(AUTOCORRECTION_PANEL)
91
92 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
93 {
94     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
95     if (markerTypesForAutoCorrection.isEmpty()) {
96         markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
97         markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
98         markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
99         markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
100     }
101     return markerTypesForAutoCorrection;
102 }
103
104 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
105 {
106     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
107     if (markerTypesForReplacement.isEmpty()) {
108         markerTypesForReplacement.append(DocumentMarker::Replacement);
109         markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
110     }
111     return markerTypesForReplacement;
112 }
113
114 static const Vector<DocumentMarker::MarkerType>& markerTypesForAppliedDictationAlternative()
115 {
116     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAppliedDictationAlternative, ());
117     if (markerTypesForAppliedDictationAlternative.isEmpty())
118         markerTypesForAppliedDictationAlternative.append(DocumentMarker::SpellCheckingExemption);
119     return markerTypesForAppliedDictationAlternative;
120 }
121
122 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers)
123 {
124     if (markers.isEmpty())
125         return true;
126
127     const String& description = markers[0]->description();
128     for (size_t i = 1; i < markers.size(); ++i) {
129         if (description != markers[i]->description())
130             return false;
131     }
132     return true;
133 }
134
135 AlternativeTextController::AlternativeTextController(Frame* frame)
136     : m_timer(this, &AlternativeTextController::timerFired)
137     , m_frame(frame)
138 {
139 }
140
141 AlternativeTextController::~AlternativeTextController()
142 {
143     dismiss(ReasonForDismissingAlternativeTextIgnored);
144 }
145
146 void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType type)
147 {
148     const double correctionPanelTimerInterval = 0.3;
149     if (!isAutomaticSpellingCorrectionEnabled())
150         return;
151
152     // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
153     if (type == AlternativeTextTypeCorrection)
154         m_alternativeTextInfo.rangeWithAlternative.clear();
155     m_alternativeTextInfo.type = type;
156     m_timer.startOneShot(correctionPanelTimerInterval);
157 }
158
159 void AlternativeTextController::stopAlternativeTextUITimer()
160 {
161     m_timer.stop();
162     m_alternativeTextInfo.rangeWithAlternative.clear();
163 }
164
165 void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection)
166 {
167     // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
168     VisibleSelection currentSelection(m_frame->selection()->selection());
169     if (currentSelection == oldSelection)
170         return;
171
172     stopAlternativeTextUITimer();
173     dismiss(ReasonForDismissingAlternativeTextIgnored);
174 }
175
176 void AlternativeTextController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
177 {
178     // Apply pending autocorrection before next round of spell checking.
179     bool doApplyCorrection = true;
180     VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
181     VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
182     if (currentWord.visibleEnd() == startOfSelection) {
183         String wordText = plainText(currentWord.toNormalizedRange().get());
184         if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
185             doApplyCorrection = false;
186     }
187     if (doApplyCorrection)
188         handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); 
189     else
190         m_alternativeTextInfo.rangeWithAlternative.clear();
191 }
192
193 bool AlternativeTextController::hasPendingCorrection() const
194 {
195     return m_alternativeTextInfo.rangeWithAlternative;
196 }
197
198 bool AlternativeTextController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
199 {
200     return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
201 }
202
203 void AlternativeTextController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
204 {
205     FloatRect boundingBox = rootViewRectForRange(rangeToReplace.get());
206     if (boundingBox.isEmpty())
207         return;
208     m_alternativeTextInfo.originalText = plainText(rangeToReplace.get());
209     m_alternativeTextInfo.rangeWithAlternative = rangeToReplace;
210     m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(replacement);
211     m_alternativeTextInfo.isActive = true;
212     if (AlternativeTextClient* client = alternativeTextClient())
213         client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, replacement, Vector<String>());
214 }
215
216 void AlternativeTextController::handleCancelOperation()
217 {
218     if (!m_alternativeTextInfo.isActive)
219         return;
220     m_alternativeTextInfo.isActive = false;
221     dismiss(ReasonForDismissingAlternativeTextCancelled);
222 }
223
224 void AlternativeTextController::dismiss(ReasonForDismissingAlternativeText reasonForDismissing)
225 {
226     if (!m_alternativeTextInfo.isActive)
227         return;
228     m_alternativeTextInfo.isActive = false;
229     m_isDismissedByEditing = true;
230     if (AlternativeTextClient* client = alternativeTextClient())
231         client->dismissAlternative(reasonForDismissing);
232 }
233
234 String AlternativeTextController::dismissSoon(ReasonForDismissingAlternativeText reasonForDismissing)
235 {
236     if (!m_alternativeTextInfo.isActive)
237         return String();
238     m_alternativeTextInfo.isActive = false;
239     m_isDismissedByEditing = true;
240     if (AlternativeTextClient* client = alternativeTextClient())
241         return client->dismissAlternativeSoon(reasonForDismissing);
242     return String();
243 }
244
245 void AlternativeTextController::applyAlternativeTextToRange(const Range* range, const String& alternative, AlternativeTextType alternativeType, const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
246 {
247     if (!range)
248         return;
249
250     ExceptionCode ec = 0;
251     RefPtr<Range> paragraphRangeContainingCorrection = range->cloneRange(ec);
252     if (ec)
253         return;
254
255     setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(range->startPosition()));
256     setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(range->endPosition()));
257
258     // After we replace the word at range rangeWithAlternative, we need to add markers to that range.
259     // However, once the replacement took place, the value of rangeWithAlternative is not valid anymore.
260     // So before we carry out the replacement, we need to store the start position of rangeWithAlternative
261     // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph
262     // to store this value. In order to obtain this offset, we need to first create a range
263     // which spans from the start of paragraph to the start position of rangeWithAlternative.
264     RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
265     if (ec)
266         return;
267
268     Position startPositionOfrangeWithAlternative = range->startPosition();
269     correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec);
270     if (ec)
271         return;
272
273     // Take note of the location of autocorrection so that we can add marker after the replacement took place.
274     int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get());
275
276     // Clone the range, since the caller of this method may want to keep the original range around.
277     RefPtr<Range> rangeWithAlternative = range->cloneRange(ec);
278     
279     int paragraphStartIndex = TextIterator::rangeLength(Range::create(m_frame->document(), m_frame->document(), 0, paragraphRangeContainingCorrection.get()->startContainer(), paragraphRangeContainingCorrection.get()->startOffset()).get());
280     applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative, alternative));
281     // Recalculate pragraphRangeContainingCorrection, since SpellingCorrectionCommand modified the DOM, such that the original paragraphRangeContainingCorrection is no longer valid. Radar: 10305315 Bugzilla: 89526
282     paragraphRangeContainingCorrection = TextIterator::rangeFromLocationAndLength(m_frame->document(), paragraphStartIndex, correctionStartOffsetInParagraph + alternative.length());
283     
284     setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
285     RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, alternative.length());
286     String newText = plainText(replacementRange.get());
287
288     // Check to see if replacement succeeded.
289     if (newText != alternative)
290         return;
291
292     DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
293     size_t size = markerTypesToAdd.size();
294     for (size_t i = 0; i < size; ++i)
295         markers->addMarker(replacementRange.get(), markerTypesToAdd[i], markerDescriptionForAppliedAlternativeText(alternativeType, markerTypesToAdd[i]));
296 }
297
298 bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate()
299 {
300     if (!m_alternativeTextInfo.rangeWithAlternative || !m_alternativeTextInfo.isActive)
301         return false;
302
303     if (m_alternativeTextInfo.type != AlternativeTextTypeCorrection)
304         return false;
305
306     Position caretPosition = m_frame->selection()->selection().start();
307
308     if (m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition) {
309         handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted));
310         return true;
311     } 
312     
313     // 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.
314     ASSERT(m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition);
315     dismiss(ReasonForDismissingAlternativeTextIgnored);
316     return false;
317 }
318
319 void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
320 {
321     if (AlternativeTextClient* client = alternativeTextClient())
322         client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction);
323     m_frame->document()->updateLayout();
324     m_frame->selection()->setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered);
325     RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
326
327     DocumentMarkerController* markers = m_frame->document()->markers();
328     markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
329     markers->addMarker(range.get(), DocumentMarker::Replacement);
330     markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
331 }
332
333 void AlternativeTextController::timerFired(Timer<AlternativeTextController>*)
334 {
335     m_isDismissedByEditing = false;
336     switch (m_alternativeTextInfo.type) {
337     case AlternativeTextTypeCorrection: {
338         VisibleSelection selection(m_frame->selection()->selection());
339         VisiblePosition start(selection.start(), selection.affinity());
340         VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
341         VisibleSelection adjacentWords = VisibleSelection(p, start);
342         m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeReplacement | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
343     }
344         break;
345     case AlternativeTextTypeReversion: {
346         if (!m_alternativeTextInfo.rangeWithAlternative)
347             break;
348         m_alternativeTextInfo.isActive = true;
349         m_alternativeTextInfo.originalText = plainText(m_alternativeTextInfo.rangeWithAlternative.get());
350         FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get());
351         if (!boundingBox.isEmpty()) {
352             const AutocorrectionAlternativeDetails* details = static_cast<const AutocorrectionAlternativeDetails*>(m_alternativeTextInfo.details.get());
353             if (AlternativeTextClient* client = alternativeTextClient())
354                 client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, details->replacementString(), Vector<String>());
355         }
356     }
357         break;
358     case AlternativeTextTypeSpellingSuggestions: {
359         if (!m_alternativeTextInfo.rangeWithAlternative || plainText(m_alternativeTextInfo.rangeWithAlternative.get()) != m_alternativeTextInfo.originalText)
360             break;
361         String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get());
362         Vector<String> suggestions;
363         textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, suggestions);
364         if (suggestions.isEmpty()) {
365             m_alternativeTextInfo.rangeWithAlternative.clear();
366             break;
367         }
368         String topSuggestion = suggestions.first();
369         suggestions.remove(0);
370         m_alternativeTextInfo.isActive = true;
371         FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get());
372         if (!boundingBox.isEmpty()) {
373             if (AlternativeTextClient* client = alternativeTextClient())
374                 client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, topSuggestion, suggestions);
375         }
376     }
377         break;
378     case AlternativeTextTypeDictationAlternatives:
379     {
380 #if USE(DICTATION_ALTERNATIVES)
381         const Range* rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative.get();
382         const DictationAlternativeDetails* details = static_cast<const DictationAlternativeDetails*>(m_alternativeTextInfo.details.get());
383         if (!rangeWithAlternative || !details || !details->dictationContext())
384             return;
385         FloatRect boundingBox = rootViewRectForRange(rangeWithAlternative);
386         m_alternativeTextInfo.isActive = true;
387         if (!boundingBox.isEmpty()) {
388             if (AlternativeTextClient* client = alternativeTextClient())
389                 client->showDictationAlternativeUI(boundingBox, details->dictationContext());
390         }
391 #endif
392     }
393         break;
394     }
395 }
396
397 void AlternativeTextController::handleAlternativeTextUIResult(const String& result)
398 {
399     Range* rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative.get();
400     if (!rangeWithAlternative || m_frame->document() != rangeWithAlternative->ownerDocument())
401         return;
402
403     String currentWord = plainText(rangeWithAlternative);
404     // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
405     if (currentWord != m_alternativeTextInfo.originalText)
406         return;
407
408     m_alternativeTextInfo.isActive = false;
409
410     switch (m_alternativeTextInfo.type) {
411     case AlternativeTextTypeCorrection:
412         if (result.length())
413             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAutocorrection());
414         else if (!m_isDismissedByEditing)
415             rangeWithAlternative->startContainer()->document()->markers()->addMarker(rangeWithAlternative, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText);
416         break;
417     case AlternativeTextTypeReversion:
418     case AlternativeTextTypeSpellingSuggestions:
419         if (result.length())
420             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForReplacement());
421         break;
422     case AlternativeTextTypeDictationAlternatives:
423         if (result.length())
424             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAppliedDictationAlternative());
425         break;
426     }
427
428     m_alternativeTextInfo.rangeWithAlternative.clear();
429 }
430
431 bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled()
432 {
433     return editorClient() && editorClient()->isAutomaticSpellingCorrectionEnabled();
434 }
435
436 FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) const
437 {
438     FrameView* view = m_frame->view();
439     if (!view)
440         return FloatRect();
441     Vector<FloatQuad> textQuads;
442     range->textQuads(textQuads);
443     FloatRect boundingRect;
444     size_t size = textQuads.size();
445     for (size_t i = 0; i < size; ++i)
446         boundingRect.unite(textQuads[i].boundingBox());
447     return view->contentsToRootView(IntRect(boundingRect));
448 }        
449
450 void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
451 {
452     VisibleSelection currentSelection(m_frame->selection()->selection());
453     // When user moves caret to the end of autocorrected word and pauses, we show the panel
454     // containing the original pre-correction word so that user can quickly revert the
455     // undesired autocorrection. Here, we start correction panel timer once we confirm that
456     // the new caret position is at the end of a word.
457     if (!currentSelection.isCaret() || currentSelection == oldSelection || !currentSelection.isContentEditable())
458         return;
459
460     VisiblePosition selectionPosition = currentSelection.start();
461     
462     // Creating a Visible position triggers a layout and there is no
463     // guarantee that the selection is still valid.
464     if (selectionPosition.isNull())
465         return;
466     
467     VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
468     if (selectionPosition != endPositionOfWord)
469         return;
470
471     Position position = endPositionOfWord.deepEquivalent();
472     if (position.anchorType() != Position::PositionIsOffsetInAnchor)
473         return;
474
475     Node* node = position.containerNode();
476     Vector<DocumentMarker*> markers = node->document()->markers()->markersFor(node);
477     size_t markerCount = markers.size();
478     for (size_t i = 0; i < markerCount; ++i) {
479         const DocumentMarker* marker = markers[i];
480         if (!marker)
481             continue;
482
483         if (respondToMarkerAtEndOfWord(*marker, position, options))
484             break;
485     }
486 }
487
488 void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* command)
489 {
490     if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
491         m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
492
493     markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command);
494     m_originalStringForLastDeletedAutocorrection = String();
495 }
496
497 void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command)
498 {
499     if (!command->wasCreateLinkCommand())
500         return;
501     RefPtr<Range> range = Range::create(m_frame->document(), command->startingSelection().start(), command->startingSelection().end());
502     if (!range)
503         return;
504     DocumentMarkerController* markers = m_frame->document()->markers();
505     markers->addMarker(range.get(), DocumentMarker::Replacement);
506     markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
507 }
508
509 AlternativeTextClient* AlternativeTextController::alternativeTextClient()
510 {
511     if (!m_frame)
512         return 0;
513
514     return m_frame->page() ? m_frame->page()->alternativeTextClient() : 0;
515 }
516
517 EditorClient* AlternativeTextController::editorClient()
518 {
519     if (!m_frame)
520         return 0;
521
522     return m_frame->page() ? m_frame->page()->editorClient() : 0;
523 }
524
525 TextCheckerClient* AlternativeTextController::textChecker()
526 {
527     if (EditorClient* owner = editorClient())
528         return owner->textChecker();
529     return 0;
530 }
531
532 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
533 {
534     if (AlternativeTextClient* client = alternativeTextClient())
535         client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString);
536 }
537
538 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
539 {
540     recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
541 }
542
543 void AlternativeTextController::markReversed(PassRefPtr<Range> changedRange)
544 {
545     changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
546     changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
547 }
548
549 void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
550 {
551     Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
552     DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers();
553     for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
554         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
555         if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
556             markers->addMarker(replacedRange.get(), markerType, replacedString);
557         else
558             markers->addMarker(replacedRange.get(), markerType);
559     }
560 }
561
562 void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
563 {
564     if (!rangeOfCorrection)
565         return;
566     DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
567     Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
568     if (correctedOnceMarkers.isEmpty())
569         return;
570
571     if (AlternativeTextClient* client = alternativeTextClient()) {
572         // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
573         // edited it to something else, and notify spellchecker accordingly.
574         if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected)
575             client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction);
576         else
577             client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction);
578     }
579
580     markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
581 }
582
583 void AlternativeTextController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
584 {
585     m_originalStringForLastDeletedAutocorrection = originalString;
586     m_positionForLastDeletedAutocorrection = position;
587 }
588
589 void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command)
590 {
591     Position endOfSelection = command->endingSelection().end();
592     if (endOfSelection != m_positionForLastDeletedAutocorrection)
593         return;
594
595     Position precedingCharacterPosition = endOfSelection.previous();
596     if (endOfSelection == precedingCharacterPosition)
597         return;
598
599     RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, endOfSelection);
600     String string = plainText(precedingCharacterRange.get());
601     if (string.isEmpty() || !isWhitespace(string[string.length() - 1]))
602         return;
603
604     // Mark this whitespace to indicate we have deleted an autocorrection following this
605     // whitespace. So if the user types the same original word again at this position, we
606     // won't autocorrect it again.
607     m_frame->document()->markers()->addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection);
608 }
609
610 bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeWithAlternative, const String& stringToBeReplaced)
611 {
612     DocumentMarkerController* markerController = m_frame->document()->markers();
613     if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::Replacement)) {
614         if (result->type == TextCheckingTypeCorrection)
615             recordSpellcheckerResponseForModifiedCorrection(rangeWithAlternative, stringToBeReplaced, result->replacement);
616         return false;
617     }
618
619     if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection))
620         return false;
621
622     Position beginningOfRange = rangeWithAlternative->startPosition();
623     Position precedingCharacterPosition = beginningOfRange.previous();
624     RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange);
625
626     Vector<DocumentMarker*> markers = markerController->markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection);
627
628     for (size_t i = 0; i < markers.size(); ++i) {
629         if (markers[i]->description() == stringToBeReplaced)
630             return false;
631     }
632
633     return true;
634 }
635
636 bool AlternativeTextController::shouldStartTimerFor(const WebCore::DocumentMarker &marker, int endOffset) const
637 {
638     return (((marker.type() == DocumentMarker::Replacement && !marker.description().isNull()) || marker.type() == DocumentMarker::Spelling || marker.type() == DocumentMarker::DictationAlternatives) && static_cast<int>(marker.endOffset()) == endOffset);
639 }
640
641 bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& marker, const Position& endOfWordPosition, FrameSelection::SetSelectionOptions options)
642 {
643     if (options & FrameSelection::DictationTriggered)
644         return false;
645     if (!shouldStartTimerFor(marker, endOfWordPosition.offsetInContainerNode()))
646         return false;
647     Node* node = endOfWordPosition.containerNode();
648     RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker.startOffset(), node, marker.endOffset());
649     if (!wordRange)
650         return false;
651     String currentWord = plainText(wordRange.get());
652     if (!currentWord.length())
653         return false;
654     m_alternativeTextInfo.originalText = currentWord;
655     switch (marker.type()) {
656     case DocumentMarker::Spelling:
657         m_alternativeTextInfo.rangeWithAlternative = wordRange;
658         m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create("");
659         startAlternativeTextUITimer(AlternativeTextTypeSpellingSuggestions);
660         break;
661     case DocumentMarker::Replacement:
662         m_alternativeTextInfo.rangeWithAlternative = wordRange;
663         m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(marker.description());
664         startAlternativeTextUITimer(AlternativeTextTypeReversion);
665         break;
666     case DocumentMarker::DictationAlternatives: {
667         const DictationMarkerDetails* markerDetails = static_cast<const DictationMarkerDetails*>(marker.details());
668         if (!markerDetails)
669             return false;
670         if (currentWord != markerDetails->originalText())
671             return false;
672         m_alternativeTextInfo.rangeWithAlternative = wordRange;
673         m_alternativeTextInfo.details = DictationAlternativeDetails::create(markerDetails->dictationContext());
674         startAlternativeTextUITimer(AlternativeTextTypeDictationAlternatives);
675     }
676         break;
677     default:
678         ASSERT_NOT_REACHED();
679         break;
680     }
681     return true;
682 }
683
684 String AlternativeTextController::markerDescriptionForAppliedAlternativeText(AlternativeTextType alternativeTextType, DocumentMarker::MarkerType markerType)
685 {
686
687     if (alternativeTextType != AlternativeTextTypeReversion && alternativeTextType != AlternativeTextTypeDictationAlternatives && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
688         return m_alternativeTextInfo.originalText;
689     return "";
690 }
691
692 #endif
693
694 bool AlternativeTextController::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent)
695 {
696     if (!m_frame)
697         return false;
698     EventTarget* target;
699     if (triggeringEvent)
700         target = triggeringEvent->target();
701     else
702         target = eventTargetNodeForDocument(m_frame->document());
703     if (!target)
704         return false;
705
706     if (FrameView* view = m_frame->view())
707         view->resetDeferredRepaintDelay();
708
709     RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame->document()->domWindow(), text, dictationAlternatives);
710     event->setUnderlyingEvent(triggeringEvent);
711
712     target->dispatchEvent(event, IGNORE_EXCEPTION);
713     return event->defaultHandled();
714 }
715
716 void AlternativeTextController::removeDictationAlternativesForMarker(const DocumentMarker* marker)
717 {
718 #if USE(DICTATION_ALTERNATIVES)
719     DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details());
720     if (AlternativeTextClient* client = alternativeTextClient())
721         client->removeDictationAlternatives(details->dictationContext());
722 #else
723     UNUSED_PARAM(marker);
724 #endif
725 }
726
727 Vector<String> AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker* marker)
728 {
729 #if USE(DICTATION_ALTERNATIVES)
730     ASSERT(marker->type() == DocumentMarker::DictationAlternatives);
731     if (AlternativeTextClient* client = alternativeTextClient()) {
732         DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details());
733         return client->dictationAlternatives(details->dictationContext());
734     }
735     return Vector<String>();
736 #else
737     UNUSED_PARAM(marker);
738     return Vector<String>();
739 #endif
740 }
741
742 void AlternativeTextController::applyDictationAlternative(const String& alternativeString)
743 {
744 #if USE(DICTATION_ALTERNATIVES)
745     Editor* editor = m_frame->editor();
746     RefPtr<Range> selection = editor->selectedRange();
747     if (!selection || !editor->shouldInsertText(alternativeString, selection.get(), EditorInsertActionPasted))
748         return;
749     DocumentMarkerController* markers = selection->startContainer()->document()->markers();
750     Vector<DocumentMarker*> dictationAlternativesMarkers = markers->markersInRange(selection.get(), DocumentMarker::DictationAlternatives);
751     for (size_t i = 0; i < dictationAlternativesMarkers.size(); ++i)
752         removeDictationAlternativesForMarker(dictationAlternativesMarkers[i]);
753
754     applyAlternativeTextToRange(selection.get(), alternativeString, AlternativeTextTypeDictationAlternatives, markerTypesForAppliedDictationAlternative());
755 #else
756     UNUSED_PARAM(alternativeString);
757 #endif
758 }
759
760 } // namespace WebCore