0d2c05e6c4a1ccb2a8c03a18e56a49be6f7ff4e5
[WebKit-https.git] / WebCore / editing / TypingCommand.cpp
1 /*
2  * Copyright (C) 2005 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 "TypingCommand.h"
28
29 #include "BeforeTextInsertedEvent.h"
30 #include "BreakBlockquoteCommand.h"
31 #include "DeleteSelectionCommand.h"
32 #include "Document.h"
33 #include "Element.h"
34 #include "Frame.h"
35 #include "InsertLineBreakCommand.h"
36 #include "InsertParagraphSeparatorCommand.h"
37 #include "InsertTextCommand.h"
38 #include "SelectionController.h"
39 #include "VisiblePosition.h"
40 #include "htmlediting.h"
41 #include "visible_units.h"
42
43 namespace WebCore {
44
45 TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, bool selectInsertedText, TextGranularity granularity)
46     : CompositeEditCommand(document), 
47       m_commandType(commandType), 
48       m_textToInsert(textToInsert), 
49       m_openForMoreTyping(true), 
50       m_applyEditing(false), 
51       m_selectInsertedText(selectInsertedText),
52       m_smartDelete(false),
53       m_granularity(granularity)
54 {
55 }
56
57 void TypingCommand::deleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity)
58 {
59     ASSERT(document);
60     
61     Frame *frame = document->frame();
62     ASSERT(frame);
63     
64     EditCommandPtr lastEditCommand = frame->lastEditCommand();
65     if (isOpenForMoreTypingCommand(lastEditCommand)) {
66         static_cast<TypingCommand *>(lastEditCommand.get())->deleteKeyPressed();
67         return;
68     }
69     
70     TypingCommand *typingCommand = new TypingCommand(document, DeleteKey, "", false, granularity);
71     typingCommand->setSmartDelete(smartDelete);
72     EditCommandPtr cmd(typingCommand);
73     cmd.apply();
74 }
75
76 void TypingCommand::forwardDeleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity)
77 {
78     ASSERT(document);
79     
80     Frame *frame = document->frame();
81     ASSERT(frame);
82     
83     EditCommandPtr lastEditCommand = frame->lastEditCommand();
84     if (isOpenForMoreTypingCommand(lastEditCommand)) {
85         static_cast<TypingCommand *>(lastEditCommand.get())->forwardDeleteKeyPressed();
86         return;
87     }
88
89     TypingCommand *typingCommand = new TypingCommand(document, ForwardDeleteKey, "", false, granularity);
90     typingCommand->setSmartDelete(smartDelete);
91     EditCommandPtr cmd(typingCommand);
92     cmd.apply();
93 }
94
95 void TypingCommand::insertText(Document *document, const String &text, bool selectInsertedText)
96 {
97     ASSERT(document);
98     
99     Frame *frame = document->frame();
100     ASSERT(frame);
101     
102     String newText = text;
103     Node* startNode = frame->selection().start().node();
104     
105     if (startNode && startNode->rootEditableElement()) {        
106         // Send khtmlBeforeTextInsertedEvent.  The event handler will update text if necessary.
107         ExceptionCode ec = 0;
108         RefPtr<BeforeTextInsertedEvent> evt = new BeforeTextInsertedEvent(text);
109         startNode->rootEditableElement()->dispatchEvent(evt, ec, true);
110         newText = evt->text();
111     }
112     
113     if (newText.isEmpty())
114         return;
115     
116     EditCommandPtr lastEditCommand = frame->lastEditCommand();
117     if (isOpenForMoreTypingCommand(lastEditCommand)) {
118         static_cast<TypingCommand *>(lastEditCommand.get())->insertText(newText, selectInsertedText);
119         return;
120     }
121
122     EditCommandPtr cmd(new TypingCommand(document, InsertText, newText, selectInsertedText));
123     cmd.apply();
124 }
125
126 void TypingCommand::insertLineBreak(Document *document)
127 {
128     ASSERT(document);
129     
130     Frame *frame = document->frame();
131     ASSERT(frame);
132     
133     EditCommandPtr lastEditCommand = frame->lastEditCommand();
134     if (isOpenForMoreTypingCommand(lastEditCommand)) {
135         static_cast<TypingCommand *>(lastEditCommand.get())->insertLineBreak();
136         return;
137     }
138
139     EditCommandPtr cmd(new TypingCommand(document, InsertLineBreak));
140     cmd.apply();
141 }
142
143 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document *document)
144 {
145     ASSERT(document);
146     
147     Frame *frame = document->frame();
148     ASSERT(frame);
149     
150     EditCommandPtr lastEditCommand = frame->lastEditCommand();
151     if (isOpenForMoreTypingCommand(lastEditCommand)) {
152         static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparatorInQuotedContent();
153         return;
154     }
155
156     EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparatorInQuotedContent));
157     cmd.apply();
158 }
159
160 void TypingCommand::insertParagraphSeparator(Document *document)
161 {
162     ASSERT(document);
163     
164     Frame *frame = document->frame();
165     ASSERT(frame);
166     
167     EditCommandPtr lastEditCommand = frame->lastEditCommand();
168     if (isOpenForMoreTypingCommand(lastEditCommand)) {
169         static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparator();
170         return;
171     }
172
173     EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparator));
174     cmd.apply();
175 }
176
177 bool TypingCommand::isOpenForMoreTypingCommand(const EditCommandPtr &cmd)
178 {
179     return cmd.isTypingCommand() &&
180         static_cast<const TypingCommand *>(cmd.get())->openForMoreTyping();
181 }
182
183 void TypingCommand::closeTyping(const EditCommandPtr &cmd)
184 {
185     if (isOpenForMoreTypingCommand(cmd))
186         static_cast<TypingCommand *>(cmd.get())->closeTyping();
187 }
188
189 void TypingCommand::doApply()
190 {
191     if (endingSelection().isNone())
192         return;
193
194     switch (m_commandType) {
195         case DeleteKey:
196             deleteKeyPressed();
197             return;
198         case ForwardDeleteKey:
199             forwardDeleteKeyPressed();
200             return;
201         case InsertLineBreak:
202             insertLineBreak();
203             return;
204         case InsertParagraphSeparator:
205             insertParagraphSeparator();
206             return;
207         case InsertParagraphSeparatorInQuotedContent:
208             insertParagraphSeparatorInQuotedContent();
209             return;
210         case InsertText:
211             insertText(m_textToInsert, m_selectInsertedText);
212             return;
213     }
214
215     ASSERT_NOT_REACHED();
216 }
217
218 EditAction TypingCommand::editingAction() const
219 {
220     return EditActionTyping;
221 }
222
223 void TypingCommand::markMisspellingsAfterTyping()
224 {
225     // Take a look at the selection that results after typing and determine whether we need to spellcheck. 
226     // Since the word containing the current selection is never marked, this does a check to
227     // see if typing made a new word that is not in the current selection. Basically, you
228     // get this by being at the end of a word and typing a space.    
229     VisiblePosition start(endingSelection().start(), endingSelection().affinity());
230     VisiblePosition previous = start.previous();
231     if (previous.isNotNull()) {
232         VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
233         VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
234         if (p1 != p2)
235             document()->frame()->markMisspellingsInAdjacentWords(p1);
236     }
237 }
238
239 void TypingCommand::typingAddedToOpenCommand()
240 {
241     markMisspellingsAfterTyping();
242     // Do not apply editing to the frame on the first time through.
243     // The frame will get told in the same way as all other commands.
244     // But since this command stays open and is used for additional typing, 
245     // we need to tell the frame here as other commands are added.
246     if (m_applyEditing) {
247         EditCommandPtr cmd(this);
248         document()->frame()->appliedEditing(cmd);
249     }
250     m_applyEditing = true;
251 }
252
253 void TypingCommand::insertText(const String &text, bool selectInsertedText)
254 {
255     // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
256     // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
257     // an existing selection; at the moment they can either put the caret after what's inserted or
258     // select what's inserted, but there's no way to "extend selection" to include both an old selection
259     // that ends just before where we want to insert text and the newly inserted text.
260     int offset = 0;
261     int newline;
262     while ((newline = text.find('\n', offset)) != -1) {
263         if (newline != offset)
264             insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
265         insertParagraphSeparator();
266         offset = newline + 1;
267     }
268     if (offset == 0)
269         insertTextRunWithoutNewlines(text, selectInsertedText);
270     else {
271         int length = text.length();
272         if (length != offset) {
273             insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
274         }
275     }
276 }
277
278 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
279 {
280     // FIXME: Improve typing style.
281     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
282     if (document()->frame()->typingStyle() || m_cmds.count() == 0) {
283         InsertTextCommand *impl = new InsertTextCommand(document());
284         EditCommandPtr cmd(impl);
285         applyCommandToComposite(cmd);
286         impl->input(text, selectInsertedText);
287     } else {
288         EditCommandPtr lastCommand = m_cmds.last();
289         if (lastCommand.isInsertTextCommand()) {
290             InsertTextCommand *impl = static_cast<InsertTextCommand *>(lastCommand.get());
291             impl->input(text, selectInsertedText);
292         } else {
293             InsertTextCommand *impl = new InsertTextCommand(document());
294             EditCommandPtr cmd(impl);
295             applyCommandToComposite(cmd);
296             impl->input(text, selectInsertedText);
297         }
298     }
299     typingAddedToOpenCommand();
300 }
301
302 void TypingCommand::insertLineBreak()
303 {
304     EditCommandPtr cmd(new InsertLineBreakCommand(document()));
305     applyCommandToComposite(cmd);
306     typingAddedToOpenCommand();
307 }
308
309 void TypingCommand::insertParagraphSeparator()
310 {
311     EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
312     applyCommandToComposite(cmd);
313     typingAddedToOpenCommand();
314 }
315
316 void TypingCommand::insertParagraphSeparatorInQuotedContent()
317 {
318     EditCommandPtr cmd(new BreakBlockquoteCommand(document()));
319     applyCommandToComposite(cmd);
320     typingAddedToOpenCommand();
321 }
322
323 void TypingCommand::deleteKeyPressed()
324 {
325     Selection selectionToDelete;
326     
327     switch (endingSelection().state()) {
328         case Selection::RANGE:
329             selectionToDelete = endingSelection();
330             break;
331         case Selection::CARET: {
332             // Handle delete at beginning-of-block case.
333             // Do nothing in the case that the caret is at the start of a
334             // root editable element or at the start of a document.
335             SelectionController sc = SelectionController(endingSelection().start(), endingSelection().end(), SEL_DEFAULT_AFFINITY);
336             sc.modify(SelectionController::EXTEND, SelectionController::BACKWARD, m_granularity);
337             Position upstreamStart = endingSelection().start().upstream();
338             VisiblePosition visibleStart = endingSelection().visibleStart();
339             if (visibleStart == startOfParagraph(visibleStart))
340                 upstreamStart = visibleStart.previous(true).deepEquivalent().upstream();
341             // When deleting tables: Select the table first, then perform the deletion
342             if (upstreamStart.node()->renderer() && upstreamStart.node()->renderer()->isTable() && upstreamStart.offset() == maxDeepOffset(upstreamStart.node())) {
343                 setEndingSelection(Selection(Position(upstreamStart.node(), 0), endingSelection().start(), DOWNSTREAM));
344                 typingAddedToOpenCommand();
345                 return;
346             }
347             selectionToDelete = sc.selection();
348             break;
349         }
350         case Selection::NONE:
351             ASSERT_NOT_REACHED();
352             break;
353     }
354     
355     if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(SelectionController(selectionToDelete))) {
356         deleteSelection(selectionToDelete, m_smartDelete);
357         setSmartDelete(false);
358         typingAddedToOpenCommand();
359     }
360 }
361
362 void TypingCommand::forwardDeleteKeyPressed()
363 {
364     Selection selectionToDelete;
365     
366     switch (endingSelection().state()) {
367         case Selection::RANGE:
368             selectionToDelete = endingSelection();
369             break;
370         case Selection::CARET: {
371             // Handle delete at beginning-of-block case.
372             // Do nothing in the case that the caret is at the start of a
373             // root editable element or at the start of a document.
374             SelectionController sc = SelectionController(endingSelection().start(), endingSelection().end(), SEL_DEFAULT_AFFINITY);
375             sc.modify(SelectionController::EXTEND, SelectionController::FORWARD, m_granularity);
376             Position downstreamEnd = endingSelection().end().downstream();
377             VisiblePosition visibleEnd = endingSelection().visibleEnd();
378             if (visibleEnd == endOfParagraph(visibleEnd))
379                 downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream();
380             // When deleting tables: Select the table first, then perform the deletion
381             if (downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.offset() == 0) {
382                 setEndingSelection(Selection(endingSelection().end(), Position(downstreamEnd.node(), maxDeepOffset(downstreamEnd.node())), DOWNSTREAM));
383                 typingAddedToOpenCommand();
384                 return;
385             }
386             selectionToDelete = sc.selection();
387             break;
388         }
389         case Selection::NONE:
390             ASSERT_NOT_REACHED();
391             break;
392     }
393     
394     if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(SelectionController(selectionToDelete))) {
395         deleteSelection(selectionToDelete, m_smartDelete);
396         setSmartDelete(false);
397         typingAddedToOpenCommand();
398     }
399 }
400
401 bool TypingCommand::preservesTypingStyle() const
402 {
403     switch (m_commandType) {
404         case DeleteKey:
405         case ForwardDeleteKey:
406         case InsertParagraphSeparator:
407         case InsertLineBreak:
408             return true;
409         case InsertParagraphSeparatorInQuotedContent:
410         case InsertText:
411             return false;
412     }
413     ASSERT_NOT_REACHED();
414     return false;
415 }
416
417 bool TypingCommand::isTypingCommand() const
418 {
419     return true;
420 }
421
422 } // namespace WebCore