/* * Copyright (C) 2010 Google Inc. All rights reserved. * * 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 "sky/engine/core/editing/SpellCheckRequester.h" #include "sky/engine/core/dom/Document.h" #include "sky/engine/core/dom/DocumentMarkerController.h" #include "sky/engine/core/dom/Node.h" #include "sky/engine/core/editing/SpellChecker.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/frame/Settings.h" #include "sky/engine/platform/text/TextCheckerClient.h" namespace blink { SpellCheckRequest::SpellCheckRequest( PassRefPtr checkingRange, PassRefPtr paragraphRange, const String& text, TextCheckingTypeMask mask, TextCheckingProcessType processType, const Vector& documentMarkersInRange, const Vector& documentMarkerOffsets, int requestNumber) : m_requester(0) , m_checkingRange(checkingRange) , m_paragraphRange(paragraphRange) , m_rootEditableElement(m_checkingRange->startContainer()->rootEditableElement()) , m_requestData(unrequestedTextCheckingSequence, text, mask, processType, documentMarkersInRange, documentMarkerOffsets) , m_requestNumber(requestNumber) { } SpellCheckRequest::~SpellCheckRequest() { } // static PassRefPtr SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, PassRefPtr checkingRange, PassRefPtr paragraphRange, int requestNumber) { ASSERT(checkingRange); ASSERT(paragraphRange); String text = checkingRange->text(); if (!text.length()) return PassRefPtr(); const DocumentMarkerVector& markers = checkingRange->ownerDocument().markers().markersInRange(checkingRange.get(), DocumentMarker::SpellCheckClientMarkers()); Vector hashes(markers.size()); Vector offsets(markers.size()); for (size_t i = 0; i < markers.size(); i++) { hashes[i] = markers[i]->hash(); offsets[i] = markers[i]->startOffset(); } return adoptRef(new SpellCheckRequest(checkingRange, paragraphRange, text, textCheckingOptions, processType, hashes, offsets, requestNumber)); } const TextCheckingRequestData& SpellCheckRequest::data() const { return m_requestData; } void SpellCheckRequest::didSucceed(const Vector& results) { if (!m_requester) return; SpellCheckRequester* requester = m_requester; m_requester = 0; requester->didCheckSucceed(m_requestData.sequence(), results); } void SpellCheckRequest::didCancel() { if (!m_requester) return; SpellCheckRequester* requester = m_requester; m_requester = 0; requester->didCheckCancel(m_requestData.sequence()); } void SpellCheckRequest::setCheckerAndSequence(SpellCheckRequester* requester, int sequence) { ASSERT(!m_requester); ASSERT(m_requestData.sequence() == unrequestedTextCheckingSequence); m_requester = requester; m_requestData.m_sequence = sequence; } void SpellCheckRequest::requesterDestroyed() { m_requester = 0; } SpellCheckRequester::SpellCheckRequester(LocalFrame& frame) : m_frame(frame) , m_lastRequestSequence(0) , m_lastProcessedSequence(0) , m_timerToProcessQueuedRequest(this, &SpellCheckRequester::timerFiredToProcessQueuedRequest) { } SpellCheckRequester::~SpellCheckRequester() { if (m_processingRequest) m_processingRequest->requesterDestroyed(); for (RequestQueue::iterator i = m_requestQueue.begin(); i != m_requestQueue.end(); ++i) (*i)->requesterDestroyed(); } TextCheckerClient& SpellCheckRequester::client() const { return m_frame.spellChecker().textChecker(); } void SpellCheckRequester::timerFiredToProcessQueuedRequest(Timer*) { ASSERT(!m_requestQueue.isEmpty()); if (m_requestQueue.isEmpty()) return; invokeRequest(m_requestQueue.takeFirst()); } bool SpellCheckRequester::isAsynchronousEnabled() const { return m_frame.settings() && m_frame.settings()->asynchronousSpellCheckingEnabled(); } bool SpellCheckRequester::canCheckAsynchronously(Range* range) const { return isCheckable(range) && isAsynchronousEnabled(); } bool SpellCheckRequester::isCheckable(Range* range) const { if (!range || !range->firstNode() || !range->firstNode()->renderer()) return false; const Node* node = range->startContainer(); if (node && node->isElementNode() && !toElement(node)->isSpellCheckingEnabled()) return false; return true; } void SpellCheckRequester::requestCheckingFor(PassRefPtr request) { if (!request || !canCheckAsynchronously(request->paragraphRange().get())) return; ASSERT(request->data().sequence() == unrequestedTextCheckingSequence); int sequence = ++m_lastRequestSequence; if (sequence == unrequestedTextCheckingSequence) sequence = ++m_lastRequestSequence; request->setCheckerAndSequence(this, sequence); if (m_timerToProcessQueuedRequest.isActive() || m_processingRequest) { enqueueRequest(request); return; } invokeRequest(request); } void SpellCheckRequester::cancelCheck() { if (m_processingRequest) m_processingRequest->didCancel(); } void SpellCheckRequester::invokeRequest(PassRefPtr request) { ASSERT(!m_processingRequest); m_processingRequest = request; client().requestCheckingOfString(m_processingRequest); } void SpellCheckRequester::enqueueRequest(PassRefPtr request) { ASSERT(request); bool continuation = false; if (!m_requestQueue.isEmpty()) { RefPtr lastRequest = m_requestQueue.last(); // It's a continuation if the number of the last request got incremented in the new one and // both apply to the same editable. continuation = request->rootEditableElement() == lastRequest->rootEditableElement() && request->requestNumber() == lastRequest->requestNumber() + 1; } // Spellcheck requests for chunks of text in the same element should not overwrite each other. if (!continuation) { for (RequestQueue::iterator it = m_requestQueue.begin(); it != m_requestQueue.end(); ++it) { if (request->rootEditableElement() != (*it)->rootEditableElement()) continue; *it = request; return; } } m_requestQueue.append(request); } void SpellCheckRequester::didCheck(int sequence, const Vector& results) { ASSERT(m_processingRequest); ASSERT(m_processingRequest->data().sequence() == sequence); if (m_processingRequest->data().sequence() != sequence) { m_requestQueue.clear(); return; } m_frame.spellChecker().markAndReplaceFor(m_processingRequest, results); if (m_lastProcessedSequence < sequence) m_lastProcessedSequence = sequence; m_processingRequest.clear(); if (!m_requestQueue.isEmpty()) m_timerToProcessQueuedRequest.startOneShot(0, FROM_HERE); } void SpellCheckRequester::didCheckSucceed(int sequence, const Vector& results) { TextCheckingRequestData requestData = m_processingRequest->data(); if (requestData.sequence() == sequence) { DocumentMarker::MarkerTypes markers = DocumentMarker::SpellCheckClientMarkers(); if (!requestData.maskContains(TextCheckingTypeSpelling)) markers.remove(DocumentMarker::Spelling); if (!requestData.maskContains(TextCheckingTypeGrammar)) markers.remove(DocumentMarker::Grammar); m_frame.document()->markers().removeMarkers(m_processingRequest->checkingRange().get(), markers); } didCheck(sequence, results); } void SpellCheckRequester::didCheckCancel(int sequence) { Vector results; didCheck(sequence, results); } } // namespace blink