diff options
| author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-05-07 11:21:11 +0200 |
|---|---|---|
| committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-05-07 11:21:11 +0200 |
| commit | 2cf6c8816a73e0132bd8fa3b509d62d7c51b6e47 (patch) | |
| tree | 988e8c5b116dd0466244ae2fe5af8ee9be926d76 /Source/WebCore/editing/AlternativeTextController.cpp | |
| parent | dd91e772430dc294e3bf478c119ef8d43c0a3358 (diff) | |
| download | qtwebkit-2cf6c8816a73e0132bd8fa3b509d62d7c51b6e47.tar.gz | |
Imported WebKit commit 7e538425aa020340619e927792f3d895061fb54b (http://svn.webkit.org/repository/webkit/trunk@116286)
Diffstat (limited to 'Source/WebCore/editing/AlternativeTextController.cpp')
| -rw-r--r-- | Source/WebCore/editing/AlternativeTextController.cpp | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/Source/WebCore/editing/AlternativeTextController.cpp b/Source/WebCore/editing/AlternativeTextController.cpp new file mode 100644 index 000000000..8d09ff1b9 --- /dev/null +++ b/Source/WebCore/editing/AlternativeTextController.cpp @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AlternativeTextController.h" + +#include "DictationAlternative.h" +#include "Document.h" +#include "DocumentMarkerController.h" +#include "EditCommand.h" +#include "EditorClient.h" +#include "Event.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "FrameView.h" +#include "Page.h" +#include "SpellingCorrectionCommand.h" +#include "TextCheckerClient.h" +#include "TextCheckingHelper.h" +#include "TextEvent.h" +#include "TextIterator.h" +#include "VisibleSelection.h" +#include "htmlediting.h" +#include "markup.h" +#include "visible_units.h" + +namespace WebCore { + +using namespace std; +using namespace WTF; + +class AutocorrectionAlternativeDetails : public AlternativeTextDetails { +public: + static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString) + { + return adoptRef(new AutocorrectionAlternativeDetails(replacementString)); + } + + const String& replacementString() const { return m_replacementString; } +private: + AutocorrectionAlternativeDetails(const String& replacementString) + : m_replacementString(replacementString) + { } + + String m_replacementString; +}; + +#if USE(AUTOCORRECTION_PANEL) + +static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() +{ + DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ()); + if (markerTypesForAutoCorrection.isEmpty()) { + markerTypesForAutoCorrection.append(DocumentMarker::Replacement); + markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator); + markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption); + markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected); + } + return markerTypesForAutoCorrection; +} + +static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement() +{ + DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ()); + if (markerTypesForReplacement.isEmpty()) { + markerTypesForReplacement.append(DocumentMarker::Replacement); + markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption); + } + return markerTypesForReplacement; +} + +static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers) +{ + if (markers.isEmpty()) + return true; + + const String& description = markers[0]->description(); + for (size_t i = 1; i < markers.size(); ++i) { + if (description != markers[i]->description()) + return false; + } + return true; +} + +AlternativeTextController::AlternativeTextController(Frame* frame) + : m_timer(this, &AlternativeTextController::timerFired) + , m_frame(frame) +{ +} + +AlternativeTextController::~AlternativeTextController() +{ + dismiss(ReasonForDismissingAlternativeTextIgnored); +} + +void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType type) +{ + const double correctionPanelTimerInterval = 0.3; + if (!isAutomaticSpellingCorrectionEnabled()) + return; + + // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it. + if (type == AlternativeTextTypeCorrection) + m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.type = type; + m_timer.startOneShot(correctionPanelTimerInterval); +} + +void AlternativeTextController::stopAlternativeTextUITimer() +{ + m_timer.stop(); + m_alternativeTextInfo.rangeWithAlternative.clear(); +} + +void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection) +{ + // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below. + VisibleSelection currentSelection(m_frame->selection()->selection()); + if (currentSelection == oldSelection) + return; + + stopAlternativeTextUITimer(); + dismiss(ReasonForDismissingAlternativeTextIgnored); +} + +void AlternativeTextController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping) +{ + // Apply pending autocorrection before next round of spell checking. + bool doApplyCorrection = true; + VisiblePosition startOfSelection = selectionAfterTyping.visibleStart(); + VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary)); + if (currentWord.visibleEnd() == startOfSelection) { + String wordText = plainText(currentWord.toNormalizedRange().get()); + if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1])) + doApplyCorrection = false; + } + if (doApplyCorrection) + handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); + else + m_alternativeTextInfo.rangeWithAlternative.clear(); +} + +bool AlternativeTextController::hasPendingCorrection() const +{ + return m_alternativeTextInfo.rangeWithAlternative; +} + +bool AlternativeTextController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const +{ + return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption); +} + +void AlternativeTextController::show(PassRefPtr<Range> rangeToReplace, const String& replacement) +{ + FloatRect boundingBox = rootViewRectForRange(rangeToReplace.get()); + if (boundingBox.isEmpty()) + return; + m_alternativeTextInfo.originalText = plainText(rangeToReplace.get()); + m_alternativeTextInfo.rangeWithAlternative = rangeToReplace; + m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(replacement); + m_alternativeTextInfo.isActive = true; + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, replacement, Vector<String>()); +} + +void AlternativeTextController::handleCancelOperation() +{ + if (!m_alternativeTextInfo.isActive) + return; + m_alternativeTextInfo.isActive = false; + dismiss(ReasonForDismissingAlternativeTextCancelled); +} + +void AlternativeTextController::dismiss(ReasonForDismissingAlternativeText reasonForDismissing) +{ + if (!m_alternativeTextInfo.isActive) + return; + m_alternativeTextInfo.isActive = false; + m_isDismissedByEditing = true; + if (AlternativeTextClient* client = alternativeTextClient()) + client->dismissAlternative(reasonForDismissing); +} + +String AlternativeTextController::dismissSoon(ReasonForDismissingAlternativeText reasonForDismissing) +{ + if (!m_alternativeTextInfo.isActive) + return String(); + m_alternativeTextInfo.isActive = false; + m_isDismissedByEditing = true; + if (AlternativeTextClient* client = alternativeTextClient()) + return client->dismissAlternativeSoon(reasonForDismissing); + return String(); +} + +void AlternativeTextController::applyAlternativeText(const String& alternative, const Vector<DocumentMarker::MarkerType>& markerTypesToAdd) +{ + if (!m_alternativeTextInfo.rangeWithAlternative) + return; + + ExceptionCode ec = 0; + RefPtr<Range> paragraphRangeContainingCorrection = m_alternativeTextInfo.rangeWithAlternative->cloneRange(ec); + if (ec) + return; + + setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_alternativeTextInfo.rangeWithAlternative->startPosition())); + setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_alternativeTextInfo.rangeWithAlternative->endPosition())); + + // After we replace the word at range rangeWithAlternative, we need to add markers to that range. + // However, once the replacement took place, the value of rangeWithAlternative is not valid anymore. + // So before we carry out the replacement, we need to store the start position of rangeWithAlternative + // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph + // to store this value. In order to obtain this offset, we need to first create a range + // which spans from the start of paragraph to the start position of rangeWithAlternative. + RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); + if (ec) + return; + + Position startPositionOfrangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative->startPosition(); + correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec); + if (ec) + return; + + // Take note of the location of autocorrection so that we can add marker after the replacement took place. + int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get()); + + // Clone the range, since the caller of this method may want to keep the original range around. + RefPtr<Range> rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative->cloneRange(ec); + applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative, alternative)); + setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start()); + RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, alternative.length()); + String newText = plainText(replacementRange.get()); + + // Check to see if replacement succeeded. + if (newText != alternative) + return; + + DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers(); + size_t size = markerTypesToAdd.size(); + for (size_t i = 0; i < size; ++i) { + DocumentMarker::MarkerType markerType = markerTypesToAdd[i]; + String description; + if (m_alternativeTextInfo.type != AlternativeTextTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)) + description = m_alternativeTextInfo.originalText; + markers->addMarker(replacementRange.get(), markerType, description); + } +} + +bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate() +{ + if (!m_alternativeTextInfo.rangeWithAlternative || !m_alternativeTextInfo.isActive) + return false; + + if (m_alternativeTextInfo.type != AlternativeTextTypeCorrection) + return false; + + Position caretPosition = m_frame->selection()->selection().start(); + + if (m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition) { + handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); + return true; + } + + // 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. + ASSERT(m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition); + dismiss(ReasonForDismissingAlternativeTextIgnored); + return false; +} + +void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) +{ + if (AlternativeTextClient* client = alternativeTextClient()) + client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction); + m_frame->document()->updateLayout(); + m_frame->selection()->setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered); + RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end()); + + DocumentMarkerController* markers = m_frame->document()->markers(); + markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); + markers->addMarker(range.get(), DocumentMarker::Replacement); + markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption); +} + +void AlternativeTextController::timerFired(Timer<AlternativeTextController>*) +{ + m_isDismissedByEditing = false; + switch (m_alternativeTextInfo.type) { + case AlternativeTextTypeCorrection: { + VisibleSelection selection(m_frame->selection()->selection()); + VisiblePosition start(selection.start(), selection.affinity()); + VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary); + VisibleSelection adjacentWords = VisibleSelection(p, start); + m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0); + } + break; + case AlternativeTextTypeReversion: { + if (!m_alternativeTextInfo.rangeWithAlternative) + break; + m_alternativeTextInfo.isActive = true; + m_alternativeTextInfo.originalText = plainText(m_alternativeTextInfo.rangeWithAlternative.get()); + FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get()); + if (!boundingBox.isEmpty()) { + const AutocorrectionAlternativeDetails* details = static_cast<const AutocorrectionAlternativeDetails*>(m_alternativeTextInfo.details.get()); + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, details->replacementString(), Vector<String>()); + } + } + break; + case AlternativeTextTypeSpellingSuggestions: { + if (!m_alternativeTextInfo.rangeWithAlternative || plainText(m_alternativeTextInfo.rangeWithAlternative.get()) != m_alternativeTextInfo.originalText) + break; + String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get()); + Vector<String> suggestions; + textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, suggestions); + if (suggestions.isEmpty()) { + m_alternativeTextInfo.rangeWithAlternative.clear(); + break; + } + String topSuggestion = suggestions.first(); + suggestions.remove(0); + m_alternativeTextInfo.isActive = true; + FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get()); + if (!boundingBox.isEmpty()) { + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, topSuggestion, suggestions); + } + } + break; + } +} + +void AlternativeTextController::handleAlternativeTextUIResult(const String& result) +{ + Range* replacedRange = m_alternativeTextInfo.rangeWithAlternative.get(); + if (!replacedRange || m_frame->document() != replacedRange->ownerDocument()) + return; + + String currentWord = plainText(m_alternativeTextInfo.rangeWithAlternative.get()); + // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered. + if (currentWord != m_alternativeTextInfo.originalText) + return; + + m_alternativeTextInfo.isActive = false; + + switch (m_alternativeTextInfo.type) { + case AlternativeTextTypeCorrection: + if (result.length()) + applyAlternativeText(result, markerTypesForAutocorrection()); + else if (!m_isDismissedByEditing) + replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText); + break; + case AlternativeTextTypeReversion: + case AlternativeTextTypeSpellingSuggestions: + if (result.length()) + applyAlternativeText(result, markerTypesForReplacement()); + break; + } + + m_alternativeTextInfo.rangeWithAlternative.clear(); +} + +bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled() +{ + return editorClient() && editorClient()->isAutomaticSpellingCorrectionEnabled(); +} + +FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) const +{ + FrameView* view = m_frame->view(); + if (!view) + return FloatRect(); + Vector<FloatQuad> textQuads; + range->textQuads(textQuads); + FloatRect boundingRect; + size_t size = textQuads.size(); + for (size_t i = 0; i < size; ++i) + boundingRect.unite(textQuads[i].boundingBox()); + return view->contentsToRootView(IntRect(boundingRect)); +} + +void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection) +{ + VisibleSelection currentSelection(m_frame->selection()->selection()); + // When user moves caret to the end of autocorrected word and pauses, we show the panel + // containing the original pre-correction word so that user can quickly revert the + // undesired autocorrection. Here, we start correction panel timer once we confirm that + // the new caret position is at the end of a word. + if (!currentSelection.isCaret() || currentSelection == oldSelection) + return; + + VisiblePosition selectionPosition = currentSelection.start(); + + // Creating a Visible position triggers a layout and there is no + // guarantee that the selection is still valid. + if (selectionPosition.isNull()) + return; + + VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary); + if (selectionPosition != endPositionOfWord) + return; + + Position position = endPositionOfWord.deepEquivalent(); + if (position.anchorType() != Position::PositionIsOffsetInAnchor) + return; + + Node* node = position.containerNode(); + int endOffset = position.offsetInContainerNode(); + Vector<DocumentMarker*> markers = node->document()->markers()->markersFor(node); + size_t markerCount = markers.size(); + for (size_t i = 0; i < markerCount; ++i) { + const DocumentMarker* marker = markers[i]; + if (!shouldStartTimerFor(marker, endOffset)) + continue; + RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker->startOffset(), node, marker->endOffset()); + String currentWord = plainText(wordRange.get()); + if (!currentWord.length()) + continue; + + m_alternativeTextInfo.rangeWithAlternative = wordRange; + m_alternativeTextInfo.originalText = currentWord; + if (marker->type() == DocumentMarker::Spelling) + startAlternativeTextUITimer(AlternativeTextTypeSpellingSuggestions); + else { + m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(marker->description()); + startAlternativeTextUITimer(AlternativeTextTypeReversion); + } + + break; + } +} + +void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* command) +{ + if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator()) + m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator); + + markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command); + m_originalStringForLastDeletedAutocorrection = String(); +} + +void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command) +{ + if (!command->wasCreateLinkCommand()) + return; + RefPtr<Range> range = Range::create(m_frame->document(), command->startingSelection().start(), command->startingSelection().end()); + if (!range) + return; + DocumentMarkerController* markers = m_frame->document()->markers(); + markers->addMarker(range.get(), DocumentMarker::Replacement); + markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption); +} + +AlternativeTextClient* AlternativeTextController::alternativeTextClient() +{ + if (!m_frame) + return 0; + + return m_frame->page() ? m_frame->page()->alternativeTextClient() : 0; +} + +EditorClient* AlternativeTextController::editorClient() +{ + if (!m_frame) + return 0; + + return m_frame->page() ? m_frame->page()->editorClient() : 0; +} + +TextCheckerClient* AlternativeTextController::textChecker() +{ + if (EditorClient* owner = editorClient()) + return owner->textChecker(); + return 0; +} + +void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString) +{ + if (AlternativeTextClient* client = alternativeTextClient()) + client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString); +} + +void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) +{ + recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get())); +} + +void AlternativeTextController::markReversed(PassRefPtr<Range> changedRange) +{ + changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); + changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption); +} + +void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) +{ + Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection(); + DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers(); + for (size_t i = 0; i < markerTypesToAdd.size(); ++i) { + DocumentMarker::MarkerType markerType = markerTypesToAdd[i]; + if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected) + markers->addMarker(replacedRange.get(), markerType, replacedString); + else + markers->addMarker(replacedRange.get(), markerType); + } +} + +void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction) +{ + if (!rangeOfCorrection) + return; + DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers(); + Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected); + if (correctedOnceMarkers.isEmpty()) + return; + + if (AlternativeTextClient* client = alternativeTextClient()) { + // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or + // edited it to something else, and notify spellchecker accordingly. + if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected) + client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction); + else + client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction); + } + + markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); +} + +void AlternativeTextController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString) +{ + m_originalStringForLastDeletedAutocorrection = originalString; + m_positionForLastDeletedAutocorrection = position; +} + +void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command) +{ + Position endOfSelection = command->endingSelection().end(); + if (endOfSelection != m_positionForLastDeletedAutocorrection) + return; + + Position precedingCharacterPosition = endOfSelection.previous(); + if (endOfSelection == precedingCharacterPosition) + return; + + RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, endOfSelection); + String string = plainText(precedingCharacterRange.get()); + if (string.isEmpty() || !isWhitespace(string[string.length() - 1])) + return; + + // Mark this whitespace to indicate we have deleted an autocorrection following this + // whitespace. So if the user types the same original word again at this position, we + // won't autocorrect it again. + m_frame->document()->markers()->addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection); +} + +bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeWithAlternative, const String& stringToBeReplaced) +{ + DocumentMarkerController* markerController = m_frame->document()->markers(); + if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::Replacement)) { + if (result->type == TextCheckingTypeCorrection) + recordSpellcheckerResponseForModifiedCorrection(rangeWithAlternative, stringToBeReplaced, result->replacement); + return false; + } + + if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection)) + return false; + + Position beginningOfRange = rangeWithAlternative->startPosition(); + Position precedingCharacterPosition = beginningOfRange.previous(); + RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange); + + Vector<DocumentMarker*> markers = markerController->markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection); + + for (size_t i = 0; i < markers.size(); ++i) { + if (markers[i]->description() == stringToBeReplaced) + return false; + } + + return true; +} + +#endif + +bool AlternativeTextController::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent) +{ + if (!m_frame) + return false; + EventTarget* target; + if (triggeringEvent) + target = triggeringEvent->target(); + else + target = eventTargetNodeForDocument(m_frame->document()); + if (!target) + return false; + + if (FrameView* view = m_frame->view()) + view->resetDeferredRepaintDelay(); + + RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame->domWindow(), text, dictationAlternatives); + event->setUnderlyingEvent(triggeringEvent); + + ExceptionCode ec; + target->dispatchEvent(event, ec); + return event->defaultHandled(); +} + +} // namespace WebCore |
