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
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.
27 #include "TypingCommand.h"
29 #include "BeforeTextInsertedEvent.h"
30 #include "BreakBlockquoteCommand.h"
31 #include "DeleteSelectionCommand.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"
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
),
54 m_granularity(granularity
),
55 m_openedByBackwardDelete(false)
59 void TypingCommand::deleteSelection(Document
* document
, bool smartDelete
)
63 Frame
* frame
= document
->frame();
66 if (!frame
->selection()->isRange())
69 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
70 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
71 static_cast<TypingCommand
*>(lastEditCommand
)->deleteSelection(smartDelete
);
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
)
84 Frame
*frame
= document
->frame();
87 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
88 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
89 static_cast<TypingCommand
*>(lastEditCommand
)->deleteKeyPressed(granularity
);
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.
103 Frame
*frame
= document
->frame();
106 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
107 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
108 static_cast<TypingCommand
*>(lastEditCommand
)->forwardDeleteKeyPressed(granularity
);
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
)
121 Frame
* frame
= document
->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
)
131 RefPtr
<Frame
> frame
= document
->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())
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
);
169 RefPtr
<TypingCommand
> cmd
= TypingCommand::create(document
, InsertText
, newText
, selectInsertedText
);
170 if (changeSelection
) {
171 cmd
->setStartingSelection(selectionForInsertion
);
172 cmd
->setEndingSelection(selectionForInsertion
);
175 if (changeSelection
) {
176 cmd
->setEndingSelection(currentSelection
);
177 frame
->selection()->setSelection(currentSelection
);
181 void TypingCommand::insertLineBreak(Document
*document
)
185 Frame
*frame
= document
->frame();
188 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
189 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
190 static_cast<TypingCommand
*>(lastEditCommand
)->insertLineBreak();
194 applyCommand(TypingCommand::create(document
, InsertLineBreak
));
197 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document
*document
)
201 Frame
*frame
= document
->frame();
204 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
205 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
206 static_cast<TypingCommand
*>(lastEditCommand
)->insertParagraphSeparatorInQuotedContent();
210 applyCommand(TypingCommand::create(document
, InsertParagraphSeparatorInQuotedContent
));
213 void TypingCommand::insertParagraphSeparator(Document
*document
)
217 Frame
*frame
= document
->frame();
220 EditCommand
* lastEditCommand
= frame
->editor()->lastEditCommand();
221 if (isOpenForMoreTypingCommand(lastEditCommand
)) {
222 static_cast<TypingCommand
*>(lastEditCommand
)->insertParagraphSeparator();
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())
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
);
254 deleteKeyPressed(m_granularity
);
256 case ForwardDeleteKey
:
257 forwardDeleteKeyPressed(m_granularity
);
259 case InsertLineBreak
:
262 case InsertParagraphSeparator
:
263 insertParagraphSeparator();
265 case InsertParagraphSeparatorInQuotedContent
:
266 insertParagraphSeparatorInQuotedContent();
269 insertText(m_textToInsert
, m_selectInsertedText
);
273 ASSERT_NOT_REACHED();
276 EditAction
TypingCommand::editingAction() const
278 return EditActionTyping
;
281 void TypingCommand::markMisspellingsAfterTyping()
283 if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled())
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
);
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.
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.
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;
327 insertTextRunWithoutNewlines(text
, selectInsertedText
);
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
);
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
;
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();
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
))
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();
410 selectionToDelete
= selection
.selection();
411 if (!startingSelection().isRange() || selectionToDelete
.base() != startingSelection().start())
412 selectionAfterUndo
= selectionToDelete
;
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());
420 case Selection::NONE
:
421 ASSERT_NOT_REACHED();
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
;
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();
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
;
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();
483 if (selectionToDelete
.start().node() == selectionToDelete
.end().node())
484 extraCharacters
= selectionToDelete
.end().offset() - selectionToDelete
.start().offset();
486 extraCharacters
= selectionToDelete
.end().offset();
487 extent
= Position(extent
.node(), extent
.offset() + extraCharacters
);
489 selectionAfterUndo
.setWithoutValidation(startingSelection().start(), extent
);
493 case Selection::NONE
:
494 ASSERT_NOT_REACHED();
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
:
518 case ForwardDeleteKey
:
519 case InsertParagraphSeparator
:
520 case InsertLineBreak
:
522 case InsertParagraphSeparatorInQuotedContent
:
526 ASSERT_NOT_REACHED();
530 bool TypingCommand::isTypingCommand() const
535 } // namespace WebCore