2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebCore / editing / TypingCommand.cpp
blobef144a113eb4a51345769da10d07f3c8b08ad1c8
1 /*
2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
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.
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.
26 #include "config.h"
27 #include "TypingCommand.h"
29 #include "BeforeTextInsertedEvent.h"
30 #include "BreakBlockquoteCommand.h"
31 #include "DeleteSelectionCommand.h"
32 #include "Document.h"
33 #include "Editor.h"
34 #include "Element.h"
35 #include "Frame.h"
36 #include "InsertLineBreakCommand.h"
37 #include "InsertParagraphSeparatorCommand.h"
38 #include "InsertTextCommand.h"
39 #include "SelectionController.h"
40 #include "VisiblePosition.h"
41 #include "htmlediting.h"
42 #include "visible_units.h"
44 namespace WebCore {
46 TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, bool selectInsertedText, TextGranularity granularity)
47 : CompositeEditCommand(document),
48 m_commandType(commandType),
49 m_textToInsert(textToInsert),
50 m_openForMoreTyping(true),
51 m_applyEditing(false),
52 m_selectInsertedText(selectInsertedText),
53 m_smartDelete(false),
54 m_granularity(granularity),
55 m_openedByBackwardDelete(false)
59 void TypingCommand::deleteSelection(Document* document, bool smartDelete)
61 ASSERT(document);
63 Frame* frame = document->frame();
64 ASSERT(frame);
66 if (!frame->selection()->isRange())
67 return;
69 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
70 if (isOpenForMoreTypingCommand(lastEditCommand)) {
71 static_cast<TypingCommand*>(lastEditCommand)->deleteSelection(smartDelete);
72 return;
75 RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, DeleteSelection, "", false);
76 typingCommand->setSmartDelete(smartDelete);
77 typingCommand->apply();
80 void TypingCommand::deleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity)
82 ASSERT(document);
84 Frame *frame = document->frame();
85 ASSERT(frame);
87 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
88 if (isOpenForMoreTypingCommand(lastEditCommand)) {
89 static_cast<TypingCommand*>(lastEditCommand)->deleteKeyPressed(granularity);
90 return;
93 RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, DeleteKey, "", false, granularity);
94 typingCommand->setSmartDelete(smartDelete);
95 typingCommand->apply();
98 void TypingCommand::forwardDeleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity)
100 // FIXME: Forward delete in TextEdit appears to open and close a new typing command.
101 ASSERT(document);
103 Frame *frame = document->frame();
104 ASSERT(frame);
106 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
107 if (isOpenForMoreTypingCommand(lastEditCommand)) {
108 static_cast<TypingCommand*>(lastEditCommand)->forwardDeleteKeyPressed(granularity);
109 return;
112 RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, ForwardDeleteKey, "", false, granularity);
113 typingCommand->setSmartDelete(smartDelete);
114 typingCommand->apply();
117 void TypingCommand::insertText(Document* document, const String& text, bool selectInsertedText, bool insertedTextIsComposition)
119 ASSERT(document);
121 Frame* frame = document->frame();
122 ASSERT(frame);
124 insertText(document, text, frame->selection()->selection(), selectInsertedText, insertedTextIsComposition);
127 void TypingCommand::insertText(Document* document, const String& text, const Selection& selectionForInsertion, bool selectInsertedText, bool insertedTextIsComposition)
129 ASSERT(document);
131 RefPtr<Frame> frame = document->frame();
132 ASSERT(frame);
134 Selection currentSelection = frame->selection()->selection();
135 bool changeSelection = currentSelection != selectionForInsertion;
137 String newText = text;
138 Node* startNode = selectionForInsertion.start().node();
140 if (startNode && startNode->rootEditableElement() && !insertedTextIsComposition) {
141 // Send BeforeTextInsertedEvent. The event handler will update text if necessary.
142 ExceptionCode ec = 0;
143 RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
144 startNode->rootEditableElement()->dispatchEvent(evt, ec);
145 newText = evt->text();
148 if (newText.isEmpty())
149 return;
151 // Set the starting and ending selection appropriately if we are using a selection
152 // that is different from the current selection. In the future, we should change EditCommand
153 // to deal with custom selections in a general way that can be used by all of the commands.
154 RefPtr<EditCommand> lastEditCommand = frame->editor()->lastEditCommand();
155 if (isOpenForMoreTypingCommand(lastEditCommand.get())) {
156 TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand.get());
157 if (changeSelection) {
158 lastTypingCommand->setStartingSelection(selectionForInsertion);
159 lastTypingCommand->setEndingSelection(selectionForInsertion);
161 lastTypingCommand->insertText(newText, selectInsertedText);
162 if (changeSelection) {
163 lastTypingCommand->setEndingSelection(currentSelection);
164 frame->selection()->setSelection(currentSelection);
166 return;
169 RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, selectInsertedText);
170 if (changeSelection) {
171 cmd->setStartingSelection(selectionForInsertion);
172 cmd->setEndingSelection(selectionForInsertion);
174 applyCommand(cmd);
175 if (changeSelection) {
176 cmd->setEndingSelection(currentSelection);
177 frame->selection()->setSelection(currentSelection);
181 void TypingCommand::insertLineBreak(Document *document)
183 ASSERT(document);
185 Frame *frame = document->frame();
186 ASSERT(frame);
188 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
189 if (isOpenForMoreTypingCommand(lastEditCommand)) {
190 static_cast<TypingCommand*>(lastEditCommand)->insertLineBreak();
191 return;
194 applyCommand(TypingCommand::create(document, InsertLineBreak));
197 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document *document)
199 ASSERT(document);
201 Frame *frame = document->frame();
202 ASSERT(frame);
204 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
205 if (isOpenForMoreTypingCommand(lastEditCommand)) {
206 static_cast<TypingCommand*>(lastEditCommand)->insertParagraphSeparatorInQuotedContent();
207 return;
210 applyCommand(TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent));
213 void TypingCommand::insertParagraphSeparator(Document *document)
215 ASSERT(document);
217 Frame *frame = document->frame();
218 ASSERT(frame);
220 EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
221 if (isOpenForMoreTypingCommand(lastEditCommand)) {
222 static_cast<TypingCommand*>(lastEditCommand)->insertParagraphSeparator();
223 return;
226 applyCommand(TypingCommand::create(document, InsertParagraphSeparator));
229 bool TypingCommand::isOpenForMoreTypingCommand(const EditCommand* cmd)
231 return cmd && cmd->isTypingCommand() && static_cast<const TypingCommand*>(cmd)->isOpenForMoreTyping();
234 void TypingCommand::closeTyping(EditCommand* cmd)
236 if (isOpenForMoreTypingCommand(cmd))
237 static_cast<TypingCommand*>(cmd)->closeTyping();
240 void TypingCommand::doApply()
242 if (endingSelection().isNone())
243 return;
245 if (m_commandType == DeleteKey)
246 if (m_commands.isEmpty())
247 m_openedByBackwardDelete = true;
249 switch (m_commandType) {
250 case DeleteSelection:
251 deleteSelection(m_smartDelete);
252 return;
253 case DeleteKey:
254 deleteKeyPressed(m_granularity);
255 return;
256 case ForwardDeleteKey:
257 forwardDeleteKeyPressed(m_granularity);
258 return;
259 case InsertLineBreak:
260 insertLineBreak();
261 return;
262 case InsertParagraphSeparator:
263 insertParagraphSeparator();
264 return;
265 case InsertParagraphSeparatorInQuotedContent:
266 insertParagraphSeparatorInQuotedContent();
267 return;
268 case InsertText:
269 insertText(m_textToInsert, m_selectInsertedText);
270 return;
273 ASSERT_NOT_REACHED();
276 EditAction TypingCommand::editingAction() const
278 return EditActionTyping;
281 void TypingCommand::markMisspellingsAfterTyping()
283 if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled())
284 return;
285 // Take a look at the selection that results after typing and determine whether we need to spellcheck.
286 // Since the word containing the current selection is never marked, this does a check to
287 // see if typing made a new word that is not in the current selection. Basically, you
288 // get this by being at the end of a word and typing a space.
289 VisiblePosition start(endingSelection().start(), endingSelection().affinity());
290 VisiblePosition previous = start.previous();
291 if (previous.isNotNull()) {
292 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
293 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
294 if (p1 != p2)
295 document()->frame()->editor()->markMisspellingsAfterTypingToPosition(p1);
299 void TypingCommand::typingAddedToOpenCommand()
301 markMisspellingsAfterTyping();
302 // Do not apply editing to the frame on the first time through.
303 // The frame will get told in the same way as all other commands.
304 // But since this command stays open and is used for additional typing,
305 // we need to tell the frame here as other commands are added.
306 if (m_applyEditing)
307 document()->frame()->editor()->appliedEditing(this);
308 m_applyEditing = true;
311 void TypingCommand::insertText(const String &text, bool selectInsertedText)
313 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
314 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
315 // an existing selection; at the moment they can either put the caret after what's inserted or
316 // select what's inserted, but there's no way to "extend selection" to include both an old selection
317 // that ends just before where we want to insert text and the newly inserted text.
318 int offset = 0;
319 int newline;
320 while ((newline = text.find('\n', offset)) != -1) {
321 if (newline != offset)
322 insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
323 insertParagraphSeparator();
324 offset = newline + 1;
326 if (offset == 0)
327 insertTextRunWithoutNewlines(text, selectInsertedText);
328 else {
329 int length = text.length();
330 if (length != offset) {
331 insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
336 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
338 RefPtr<InsertTextCommand> command;
339 if (!document()->frame()->typingStyle() && !m_commands.isEmpty()) {
340 EditCommand* lastCommand = m_commands.last().get();
341 if (lastCommand->isInsertTextCommand())
342 command = static_cast<InsertTextCommand*>(lastCommand);
344 if (!command) {
345 command = InsertTextCommand::create(document());
346 applyCommandToComposite(command);
348 command->input(text, selectInsertedText);
349 typingAddedToOpenCommand();
352 void TypingCommand::insertLineBreak()
354 applyCommandToComposite(InsertLineBreakCommand::create(document()));
355 typingAddedToOpenCommand();
358 void TypingCommand::insertParagraphSeparator()
360 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
361 typingAddedToOpenCommand();
364 void TypingCommand::insertParagraphSeparatorInQuotedContent()
366 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
367 typingAddedToOpenCommand();
370 void TypingCommand::deleteKeyPressed(TextGranularity granularity)
372 Selection selectionToDelete;
373 Selection selectionAfterUndo;
375 switch (endingSelection().state()) {
376 case Selection::RANGE:
377 selectionToDelete = endingSelection();
378 selectionAfterUndo = selectionToDelete;
379 break;
380 case Selection::CARET: {
381 m_smartDelete = false;
383 SelectionController selection;
384 selection.setSelection(endingSelection());
385 selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity);
387 // When the caret is at the start of the editable area in an empty list item, break out of the list item.
388 if (endingSelection().visibleStart().previous(true).isNull()) {
389 if (breakOutOfEmptyListItem()) {
390 typingAddedToOpenCommand();
391 return;
395 VisiblePosition visibleStart(endingSelection().visibleStart());
396 // If the caret is at the start of a paragraph after a table, move content into the last table cell.
397 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(true))) {
398 // Unless the caret is just before a table. We don't want to move a table into the last table cell.
399 if (isLastPositionBeforeTable(visibleStart))
400 return;
401 // Extend the selection backward into the last cell, then deletion will handle the move.
402 selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity);
403 // If the caret is just after a table, select the table and don't delete anything.
404 } else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
405 setEndingSelection(Selection(Position(table, 0), endingSelection().start(), DOWNSTREAM));
406 typingAddedToOpenCommand();
407 return;
410 selectionToDelete = selection.selection();
411 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
412 selectionAfterUndo = selectionToDelete;
413 else
414 // It's a little tricky to compute what the starting selection would have been in the original document.
415 // We can't let the Selection class's validation kick in or it'll adjust for us based on
416 // the current state of the document and we'll get the wrong result.
417 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
418 break;
420 case Selection::NONE:
421 ASSERT_NOT_REACHED();
422 break;
425 if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) {
426 // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
427 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
428 // more text than you insert. In that case all of the text that was around originally should be selected.
429 if (m_openedByBackwardDelete)
430 setStartingSelection(selectionAfterUndo);
431 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
432 setSmartDelete(false);
433 typingAddedToOpenCommand();
437 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity)
439 Selection selectionToDelete;
440 Selection selectionAfterUndo;
442 switch (endingSelection().state()) {
443 case Selection::RANGE:
444 selectionToDelete = endingSelection();
445 selectionAfterUndo = selectionToDelete;
446 break;
447 case Selection::CARET: {
448 m_smartDelete = false;
450 // Handle delete at beginning-of-block case.
451 // Do nothing in the case that the caret is at the start of a
452 // root editable element or at the start of a document.
453 SelectionController selection;
454 selection.setSelection(endingSelection());
455 selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, granularity);
456 Position downstreamEnd = endingSelection().end().downstream();
457 VisiblePosition visibleEnd = endingSelection().visibleEnd();
458 if (visibleEnd == endOfParagraph(visibleEnd))
459 downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream();
460 // When deleting tables: Select the table first, then perform the deletion
461 if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.offset() == 0) {
462 setEndingSelection(Selection(endingSelection().end(), Position(downstreamEnd.node(), maxDeepOffset(downstreamEnd.node())), DOWNSTREAM));
463 typingAddedToOpenCommand();
464 return;
467 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
468 if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
469 selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, CharacterGranularity);
471 selectionToDelete = selection.selection();
472 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
473 selectionAfterUndo = selectionToDelete;
474 else {
475 // It's a little tricky to compute what the starting selection would have been in the original document.
476 // We can't let the Selection class's validation kick in or it'll adjust for us based on
477 // the current state of the document and we'll get the wrong result.
478 Position extent = startingSelection().end();
479 if (extent.node() != selectionToDelete.end().node())
480 extent = selectionToDelete.extent();
481 else {
482 int extraCharacters;
483 if (selectionToDelete.start().node() == selectionToDelete.end().node())
484 extraCharacters = selectionToDelete.end().offset() - selectionToDelete.start().offset();
485 else
486 extraCharacters = selectionToDelete.end().offset();
487 extent = Position(extent.node(), extent.offset() + extraCharacters);
489 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
491 break;
493 case Selection::NONE:
494 ASSERT_NOT_REACHED();
495 break;
498 if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) {
499 // make undo select what was deleted
500 setStartingSelection(selectionAfterUndo);
501 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
502 setSmartDelete(false);
503 typingAddedToOpenCommand();
507 void TypingCommand::deleteSelection(bool smartDelete)
509 CompositeEditCommand::deleteSelection(smartDelete);
510 typingAddedToOpenCommand();
513 bool TypingCommand::preservesTypingStyle() const
515 switch (m_commandType) {
516 case DeleteSelection:
517 case DeleteKey:
518 case ForwardDeleteKey:
519 case InsertParagraphSeparator:
520 case InsertLineBreak:
521 return true;
522 case InsertParagraphSeparatorInQuotedContent:
523 case InsertText:
524 return false;
526 ASSERT_NOT_REACHED();
527 return false;
530 bool TypingCommand::isTypingCommand() const
532 return true;
535 } // namespace WebCore