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