1/*
2 * Copyright (C) 2012 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DictationCommand.h"
28
29#include "AlternativeTextController.h"
30#include "Document.h"
31#include "DocumentMarkerController.h"
32#include "Frame.h"
33#include "FrameSelection.h"
34#include "InsertParagraphSeparatorCommand.h"
35#include "InsertTextCommand.h"
36#include "Text.h"
37
38namespace WebCore {
39
40class DictationCommandLineOperation {
41public:
42 DictationCommandLineOperation(DictationCommand* dictationCommand)
43 : m_dictationCommand(dictationCommand)
44 { }
45
46 void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const
47 {
48 if (lineLength > 0)
49 m_dictationCommand->insertTextRunWithoutNewlines(lineOffset, lineLength);
50 if (!isLastLine)
51 m_dictationCommand->insertParagraphSeparator();
52 }
53private:
54 DictationCommand* m_dictationCommand;
55};
56
57class DictationMarkerSupplier : public TextInsertionMarkerSupplier {
58public:
59 static Ref<DictationMarkerSupplier> create(const Vector<DictationAlternative>& alternatives)
60 {
61 return adoptRef(*new DictationMarkerSupplier(alternatives));
62 }
63
64 void addMarkersToTextNode(Text& textNode, unsigned offsetOfInsertion, const String& textToBeInserted) override
65 {
66 auto& markerController = textNode.document().markers();
67 for (auto& alternative : m_alternatives) {
68 DocumentMarker::DictationData data { alternative.dictationContext, textToBeInserted.substring(alternative.rangeStart, alternative.rangeLength) };
69 markerController.addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::DictationAlternatives, WTFMove(data));
70 markerController.addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::SpellCheckingExemption);
71 }
72 }
73
74protected:
75 DictationMarkerSupplier(const Vector<DictationAlternative>& alternatives)
76 : m_alternatives(alternatives)
77 {
78 }
79private:
80 Vector<DictationAlternative> m_alternatives;
81};
82
83DictationCommand::DictationCommand(Document& document, const String& text, const Vector<DictationAlternative>& alternatives)
84 : TextInsertionBaseCommand(document)
85 , m_textToInsert(text)
86 , m_alternatives(alternatives)
87{
88}
89
90void DictationCommand::insertText(Document& document, const String& text, const Vector<DictationAlternative>& alternatives, const VisibleSelection& selectionForInsertion)
91{
92 RefPtr<Frame> frame = document.frame();
93 ASSERT(frame);
94
95 VisibleSelection currentSelection = frame->selection().selection();
96
97 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, false);
98
99 RefPtr<DictationCommand> cmd;
100 if (newText == text)
101 cmd = DictationCommand::create(document, newText, alternatives);
102 else
103 // If the text was modified before insertion, the location of dictation alternatives
104 // will not be valid anymore. We will just drop the alternatives.
105 cmd = DictationCommand::create(document, newText, Vector<DictationAlternative>());
106 applyTextInsertionCommand(frame.get(), *cmd, selectionForInsertion, currentSelection);
107}
108
109void DictationCommand::doApply()
110{
111 DictationCommandLineOperation operation(this);
112 forEachLineInString(m_textToInsert, operation);
113 postTextStateChangeNotification(AXTextEditTypeDictation, m_textToInsert);
114}
115
116void DictationCommand::insertTextRunWithoutNewlines(size_t lineStart, size_t lineLength)
117{
118 Vector<DictationAlternative> alternativesInLine;
119 collectDictationAlternativesInRange(lineStart, lineLength, alternativesInLine);
120 auto command = InsertTextCommand::createWithMarkerSupplier(document(), m_textToInsert.substring(lineStart, lineLength), DictationMarkerSupplier::create(alternativesInLine), EditAction::Dictation);
121 applyCommandToComposite(WTFMove(command), endingSelection());
122}
123
124void DictationCommand::insertParagraphSeparator()
125{
126 if (!canAppendNewLineFeedToSelection(endingSelection()))
127 return;
128
129 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), false, false, EditAction::Dictation));
130}
131
132void DictationCommand::collectDictationAlternativesInRange(size_t rangeStart, size_t rangeLength, Vector<DictationAlternative>& alternatives)
133{
134 for (auto& alternative : m_alternatives) {
135 if (alternative.rangeStart >= rangeStart && (alternative.rangeStart + alternative.rangeLength) <= rangeStart + rangeLength)
136 alternatives.append(DictationAlternative(alternative.rangeStart - rangeStart, alternative.rangeLength, alternative.dictationContext));
137 }
138
139}
140
141}
142