2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "AlternativeTextController.h"
30 #include "DictationAlternative.h"
32 #include "DocumentMarkerController.h"
33 #include "EditCommand.h"
35 #include "EditorClient.h"
37 #include "ExceptionCodePlaceholder.h"
38 #include "FloatQuad.h"
40 #include "FrameView.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"
57 class AutocorrectionAlternativeDetails : public AlternativeTextDetails {
59 static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString)
61 return adoptRef(new AutocorrectionAlternativeDetails(replacementString));
64 const String& replacementString() const { return m_replacementString; }
66 AutocorrectionAlternativeDetails(const String& replacementString)
67 : m_replacementString(replacementString)
70 String m_replacementString;
73 class DictationAlternativeDetails : public AlternativeTextDetails {
75 static PassRefPtr<DictationAlternativeDetails> create(uint64_t dictationContext)
77 return adoptRef(new DictationAlternativeDetails(dictationContext));
80 uint64_t dictationContext() const { return m_dictationContext; }
83 DictationAlternativeDetails(uint64_t dictationContext)
84 : m_dictationContext(dictationContext)
87 uint64_t m_dictationContext;
90 #if USE(AUTOCORRECTION_PANEL)
92 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
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);
101 return markerTypesForAutoCorrection;
104 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
106 DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
107 if (markerTypesForReplacement.isEmpty()) {
108 markerTypesForReplacement.append(DocumentMarker::Replacement);
109 markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
111 return markerTypesForReplacement;
114 static const Vector<DocumentMarker::MarkerType>& markerTypesForAppliedDictationAlternative()
116 DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAppliedDictationAlternative, ());
117 if (markerTypesForAppliedDictationAlternative.isEmpty())
118 markerTypesForAppliedDictationAlternative.append(DocumentMarker::SpellCheckingExemption);
119 return markerTypesForAppliedDictationAlternative;
122 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers)
124 if (markers.isEmpty())
127 const String& description = markers[0]->description();
128 for (size_t i = 1; i < markers.size(); ++i) {
129 if (description != markers[i]->description())
135 AlternativeTextController::AlternativeTextController(Frame* frame)
136 : m_timer(this, &AlternativeTextController::timerFired)
141 AlternativeTextController::~AlternativeTextController()
143 dismiss(ReasonForDismissingAlternativeTextIgnored);
146 void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType type)
148 const double correctionPanelTimerInterval = 0.3;
149 if (!isAutomaticSpellingCorrectionEnabled())
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);
159 void AlternativeTextController::stopAlternativeTextUITimer()
162 m_alternativeTextInfo.rangeWithAlternative.clear();
165 void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection)
167 // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
168 VisibleSelection currentSelection(m_frame->selection()->selection());
169 if (currentSelection == oldSelection)
172 stopAlternativeTextUITimer();
173 dismiss(ReasonForDismissingAlternativeTextIgnored);
176 void AlternativeTextController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
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;
187 if (doApplyCorrection)
188 handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted));
190 m_alternativeTextInfo.rangeWithAlternative.clear();
193 bool AlternativeTextController::hasPendingCorrection() const
195 return m_alternativeTextInfo.rangeWithAlternative;
198 bool AlternativeTextController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
200 return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
203 void AlternativeTextController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
205 FloatRect boundingBox = rootViewRectForRange(rangeToReplace.get());
206 if (boundingBox.isEmpty())
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>());
216 void AlternativeTextController::handleCancelOperation()
218 if (!m_alternativeTextInfo.isActive)
220 m_alternativeTextInfo.isActive = false;
221 dismiss(ReasonForDismissingAlternativeTextCancelled);
224 void AlternativeTextController::dismiss(ReasonForDismissingAlternativeText reasonForDismissing)
226 if (!m_alternativeTextInfo.isActive)
228 m_alternativeTextInfo.isActive = false;
229 m_isDismissedByEditing = true;
230 if (AlternativeTextClient* client = alternativeTextClient())
231 client->dismissAlternative(reasonForDismissing);
234 String AlternativeTextController::dismissSoon(ReasonForDismissingAlternativeText reasonForDismissing)
236 if (!m_alternativeTextInfo.isActive)
238 m_alternativeTextInfo.isActive = false;
239 m_isDismissedByEditing = true;
240 if (AlternativeTextClient* client = alternativeTextClient())
241 return client->dismissAlternativeSoon(reasonForDismissing);
245 void AlternativeTextController::applyAlternativeTextToRange(const Range* range, const String& alternative, AlternativeTextType alternativeType, const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
250 ExceptionCode ec = 0;
251 RefPtr<Range> paragraphRangeContainingCorrection = range->cloneRange(ec);
255 setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(range->startPosition()));
256 setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(range->endPosition()));
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());
268 Position startPositionOfrangeWithAlternative = range->startPosition();
269 correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec);
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());
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);
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());
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());
288 // Check to see if replacement succeeded.
289 if (newText != alternative)
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]));
298 bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate()
300 if (!m_alternativeTextInfo.rangeWithAlternative || !m_alternativeTextInfo.isActive)
303 if (m_alternativeTextInfo.type != AlternativeTextTypeCorrection)
306 Position caretPosition = m_frame->selection()->selection().start();
308 if (m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition) {
309 handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted));
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);
319 void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
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());
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);
333 void AlternativeTextController::timerFired(Timer<AlternativeTextController>*)
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);
345 case AlternativeTextTypeReversion: {
346 if (!m_alternativeTextInfo.rangeWithAlternative)
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>());
358 case AlternativeTextTypeSpellingSuggestions: {
359 if (!m_alternativeTextInfo.rangeWithAlternative || plainText(m_alternativeTextInfo.rangeWithAlternative.get()) != m_alternativeTextInfo.originalText)
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();
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);
378 case AlternativeTextTypeDictationAlternatives:
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())
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());
397 void AlternativeTextController::handleAlternativeTextUIResult(const String& result)
399 Range* rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative.get();
400 if (!rangeWithAlternative || m_frame->document() != rangeWithAlternative->ownerDocument())
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)
408 m_alternativeTextInfo.isActive = false;
410 switch (m_alternativeTextInfo.type) {
411 case AlternativeTextTypeCorrection:
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);
417 case AlternativeTextTypeReversion:
418 case AlternativeTextTypeSpellingSuggestions:
420 applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForReplacement());
422 case AlternativeTextTypeDictationAlternatives:
424 applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAppliedDictationAlternative());
428 m_alternativeTextInfo.rangeWithAlternative.clear();
431 bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled()
433 return editorClient() && editorClient()->isAutomaticSpellingCorrectionEnabled();
436 FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) const
438 FrameView* view = m_frame->view();
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));
450 void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
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())
460 VisiblePosition selectionPosition = currentSelection.start();
462 // Creating a Visible position triggers a layout and there is no
463 // guarantee that the selection is still valid.
464 if (selectionPosition.isNull())
467 VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
468 if (selectionPosition != endPositionOfWord)
471 Position position = endPositionOfWord.deepEquivalent();
472 if (position.anchorType() != Position::PositionIsOffsetInAnchor)
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];
483 if (respondToMarkerAtEndOfWord(*marker, position, options))
488 void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* command)
490 if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
491 m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
493 markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command);
494 m_originalStringForLastDeletedAutocorrection = String();
497 void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command)
499 if (!command->wasCreateLinkCommand())
501 RefPtr<Range> range = Range::create(m_frame->document(), command->startingSelection().start(), command->startingSelection().end());
504 DocumentMarkerController* markers = m_frame->document()->markers();
505 markers->addMarker(range.get(), DocumentMarker::Replacement);
506 markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
509 AlternativeTextClient* AlternativeTextController::alternativeTextClient()
514 return m_frame->page() ? m_frame->page()->alternativeTextClient() : 0;
517 EditorClient* AlternativeTextController::editorClient()
522 return m_frame->page() ? m_frame->page()->editorClient() : 0;
525 TextCheckerClient* AlternativeTextController::textChecker()
527 if (EditorClient* owner = editorClient())
528 return owner->textChecker();
532 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
534 if (AlternativeTextClient* client = alternativeTextClient())
535 client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString);
538 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
540 recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
543 void AlternativeTextController::markReversed(PassRefPtr<Range> changedRange)
545 changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
546 changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
549 void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
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);
558 markers->addMarker(replacedRange.get(), markerType);
562 void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
564 if (!rangeOfCorrection)
566 DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
567 Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
568 if (correctedOnceMarkers.isEmpty())
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);
577 client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction);
580 markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
583 void AlternativeTextController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
585 m_originalStringForLastDeletedAutocorrection = originalString;
586 m_positionForLastDeletedAutocorrection = position;
589 void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command)
591 Position endOfSelection = command->endingSelection().end();
592 if (endOfSelection != m_positionForLastDeletedAutocorrection)
595 Position precedingCharacterPosition = endOfSelection.previous();
596 if (endOfSelection == precedingCharacterPosition)
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]))
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);
610 bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeWithAlternative, const String& stringToBeReplaced)
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);
619 if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection))
622 Position beginningOfRange = rangeWithAlternative->startPosition();
623 Position precedingCharacterPosition = beginningOfRange.previous();
624 RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange);
626 Vector<DocumentMarker*> markers = markerController->markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection);
628 for (size_t i = 0; i < markers.size(); ++i) {
629 if (markers[i]->description() == stringToBeReplaced)
636 bool AlternativeTextController::shouldStartTimerFor(const WebCore::DocumentMarker &marker, int endOffset) const
638 return (((marker.type() == DocumentMarker::Replacement && !marker.description().isNull()) || marker.type() == DocumentMarker::Spelling || marker.type() == DocumentMarker::DictationAlternatives) && static_cast<int>(marker.endOffset()) == endOffset);
641 bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& marker, const Position& endOfWordPosition, FrameSelection::SetSelectionOptions options)
643 if (options & FrameSelection::DictationTriggered)
645 if (!shouldStartTimerFor(marker, endOfWordPosition.offsetInContainerNode()))
647 Node* node = endOfWordPosition.containerNode();
648 RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker.startOffset(), node, marker.endOffset());
651 String currentWord = plainText(wordRange.get());
652 if (!currentWord.length())
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);
661 case DocumentMarker::Replacement:
662 m_alternativeTextInfo.rangeWithAlternative = wordRange;
663 m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(marker.description());
664 startAlternativeTextUITimer(AlternativeTextTypeReversion);
666 case DocumentMarker::DictationAlternatives: {
667 const DictationMarkerDetails* markerDetails = static_cast<const DictationMarkerDetails*>(marker.details());
670 if (currentWord != markerDetails->originalText())
672 m_alternativeTextInfo.rangeWithAlternative = wordRange;
673 m_alternativeTextInfo.details = DictationAlternativeDetails::create(markerDetails->dictationContext());
674 startAlternativeTextUITimer(AlternativeTextTypeDictationAlternatives);
678 ASSERT_NOT_REACHED();
684 String AlternativeTextController::markerDescriptionForAppliedAlternativeText(AlternativeTextType alternativeTextType, DocumentMarker::MarkerType markerType)
687 if (alternativeTextType != AlternativeTextTypeReversion && alternativeTextType != AlternativeTextTypeDictationAlternatives && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
688 return m_alternativeTextInfo.originalText;
694 bool AlternativeTextController::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent)
700 target = triggeringEvent->target();
702 target = eventTargetNodeForDocument(m_frame->document());
706 if (FrameView* view = m_frame->view())
707 view->resetDeferredRepaintDelay();
709 RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame->document()->domWindow(), text, dictationAlternatives);
710 event->setUnderlyingEvent(triggeringEvent);
712 target->dispatchEvent(event, IGNORE_EXCEPTION);
713 return event->defaultHandled();
716 void AlternativeTextController::removeDictationAlternativesForMarker(const DocumentMarker* marker)
718 #if USE(DICTATION_ALTERNATIVES)
719 DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details());
720 if (AlternativeTextClient* client = alternativeTextClient())
721 client->removeDictationAlternatives(details->dictationContext());
723 UNUSED_PARAM(marker);
727 Vector<String> AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker* marker)
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());
735 return Vector<String>();
737 UNUSED_PARAM(marker);
738 return Vector<String>();
742 void AlternativeTextController::applyDictationAlternative(const String& alternativeString)
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))
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]);
754 applyAlternativeTextToRange(selection.get(), alternativeString, AlternativeTextTypeDictationAlternatives, markerTypesForAppliedDictationAlternative());
756 UNUSED_PARAM(alternativeString);
760 } // namespace WebCore