OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2005, 2006, 2007, 2008 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 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 "core/editing/TypingCommand.h" | |
28 | |
29 #include "core/HTMLNames.h" | |
30 #include "core/dom/Document.h" | |
31 #include "core/dom/Element.h" | |
32 #include "core/dom/ElementTraversal.h" | |
33 #include "core/editing/BreakBlockquoteCommand.h" | |
34 #include "core/editing/EditingUtilities.h" | |
35 #include "core/editing/Editor.h" | |
36 #include "core/editing/FrameSelection.h" | |
37 #include "core/editing/InsertLineBreakCommand.h" | |
38 #include "core/editing/InsertParagraphSeparatorCommand.h" | |
39 #include "core/editing/InsertTextCommand.h" | |
40 #include "core/editing/VisiblePosition.h" | |
41 #include "core/editing/VisibleUnits.h" | |
42 #include "core/editing/spellcheck/SpellChecker.h" | |
43 #include "core/frame/LocalFrame.h" | |
44 #include "core/html/HTMLBRElement.h" | |
45 #include "core/layout/LayoutObject.h" | |
46 | |
47 namespace blink { | |
48 | |
49 using namespace HTMLNames; | |
50 | |
51 class TypingCommandLineOperation { | |
52 STACK_ALLOCATED(); | |
53 public: | |
54 TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInserted
Text, const String& text) | |
55 : m_typingCommand(typingCommand) | |
56 , m_selectInsertedText(selectInsertedText) | |
57 , m_text(text) | |
58 { } | |
59 | |
60 void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const | |
61 { | |
62 if (isLastLine) { | |
63 if (!lineOffset || lineLength > 0) | |
64 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(l
ineOffset, lineLength), m_selectInsertedText); | |
65 } else { | |
66 if (lineLength > 0) | |
67 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(l
ineOffset, lineLength), false); | |
68 m_typingCommand->insertParagraphSeparator(); | |
69 } | |
70 } | |
71 | |
72 private: | |
73 RawPtrWillBeMember<TypingCommand> m_typingCommand; | |
74 bool m_selectInsertedText; | |
75 const String& m_text; | |
76 }; | |
77 | |
78 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, con
st String &textToInsert, Options options, TextGranularity granularity, TextCompo
sitionType compositionType) | |
79 : TextInsertionBaseCommand(document) | |
80 , m_commandType(commandType) | |
81 , m_textToInsert(textToInsert) | |
82 , m_openForMoreTyping(true) | |
83 , m_selectInsertedText(options & SelectInsertedText) | |
84 , m_smartDelete(options & SmartDelete) | |
85 , m_granularity(granularity) | |
86 , m_compositionType(compositionType) | |
87 , m_killRing(options & KillRing) | |
88 , m_openedByBackwardDelete(false) | |
89 , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndica
tor) | |
90 , m_shouldPreventSpellChecking(options & PreventSpellChecking) | |
91 { | |
92 updatePreservesTypingStyle(m_commandType); | |
93 } | |
94 | |
95 void TypingCommand::deleteSelection(Document& document, Options options) | |
96 { | |
97 LocalFrame* frame = document.frame(); | |
98 ASSERT(frame); | |
99 | |
100 if (!frame->selection().isRange()) | |
101 return; | |
102 | |
103 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(frame)) { | |
104 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellC
hecking); | |
105 lastTypingCommand->deleteSelection(options & SmartDelete); | |
106 return; | |
107 } | |
108 | |
109 TypingCommand::create(document, DeleteSelection, "", options)->apply(); | |
110 } | |
111 | |
112 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGr
anularity granularity) | |
113 { | |
114 if (granularity == CharacterGranularity) { | |
115 LocalFrame* frame = document.frame(); | |
116 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingComm
andIfStillOpenForTyping(frame)) { | |
117 // If the last typing command is not Delete, open a new typing comma
nd. | |
118 // We need to group continuous delete commands alone in a single typ
ing command. | |
119 if (lastTypingCommand->commandTypeOfOpenCommand() == DeleteKey) { | |
120 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand
.get(), frame); | |
121 lastTypingCommand->setShouldPreventSpellChecking(options & Preve
ntSpellChecking); | |
122 lastTypingCommand->deleteKeyPressed(granularity, options & KillR
ing); | |
123 return; | |
124 } | |
125 } | |
126 } | |
127 | |
128 TypingCommand::create(document, DeleteKey, "", options, granularity)->apply(
); | |
129 } | |
130 | |
131 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options,
TextGranularity granularity) | |
132 { | |
133 // FIXME: Forward delete in TextEdit appears to open and close a new typing
command. | |
134 if (granularity == CharacterGranularity) { | |
135 LocalFrame* frame = document.frame(); | |
136 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingComm
andIfStillOpenForTyping(frame)) { | |
137 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get
(), frame); | |
138 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSp
ellChecking); | |
139 lastTypingCommand->forwardDeleteKeyPressed(granularity, options & Ki
llRing); | |
140 return; | |
141 } | |
142 } | |
143 | |
144 TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)-
>apply(); | |
145 } | |
146 | |
147 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand
* typingCommand, LocalFrame* frame) | |
148 { | |
149 ASSERT(frame); | |
150 VisibleSelection currentSelection = frame->selection().selection(); | |
151 if (VisibleSelection::InDOMTree::equalSelections(currentSelection, typingCom
mand->endingSelection())) | |
152 return; | |
153 | |
154 typingCommand->setStartingSelection(currentSelection); | |
155 typingCommand->setEndingSelection(currentSelection); | |
156 } | |
157 | |
158 void TypingCommand::insertText(Document& document, const String& text, Options o
ptions, TextCompositionType composition) | |
159 { | |
160 LocalFrame* frame = document.frame(); | |
161 ASSERT(frame); | |
162 | |
163 if (!text.isEmpty()) | |
164 document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(
isSpaceOrNewline(text[0])); | |
165 | |
166 insertText(document, text, frame->selection().selection(), options, composit
ion); | |
167 } | |
168 | |
169 // FIXME: We shouldn't need to take selectionForInsertion. It should be identica
l to FrameSelection's current selection. | |
170 void TypingCommand::insertText(Document& document, const String& text, const Vis
ibleSelection& selectionForInsertion, Options options, TextCompositionType compo
sitionType) | |
171 { | |
172 RefPtrWillBeRawPtr<LocalFrame> frame = document.frame(); | |
173 ASSERT(frame); | |
174 | |
175 VisibleSelection currentSelection = frame->selection().selection(); | |
176 | |
177 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion
, compositionType == TextCompositionUpdate); | |
178 | |
179 // Set the starting and ending selection appropriately if we are using a sel
ection | |
180 // that is different from the current selection. In the future, we should c
hange EditCommand | |
181 // to deal with custom selections in a general way that can be used by all o
f the commands. | |
182 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(frame.get())) { | |
183 if (!VisibleSelection::InDOMTree::equalSelections(lastTypingCommand->end
ingSelection(), selectionForInsertion)) { | |
184 lastTypingCommand->setStartingSelection(selectionForInsertion); | |
185 lastTypingCommand->setEndingSelection(selectionForInsertion); | |
186 } | |
187 | |
188 lastTypingCommand->setCompositionType(compositionType); | |
189 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & Reta
inAutocorrectionIndicator); | |
190 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellC
hecking); | |
191 lastTypingCommand->insertText(newText, options & SelectInsertedText); | |
192 return; | |
193 } | |
194 | |
195 RefPtrWillBeRawPtr<TypingCommand> cmd = TypingCommand::create(document, Inse
rtText, newText, options, compositionType); | |
196 applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSe
lection); | |
197 } | |
198 | |
199 void TypingCommand::insertLineBreak(Document& document, Options options) | |
200 { | |
201 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(document.frame())) { | |
202 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & Reta
inAutocorrectionIndicator); | |
203 lastTypingCommand->insertLineBreak(); | |
204 return; | |
205 } | |
206 | |
207 TypingCommand::create(document, InsertLineBreak, "", options)->apply(); | |
208 } | |
209 | |
210 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document) | |
211 { | |
212 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(document.frame())) { | |
213 lastTypingCommand->insertParagraphSeparatorInQuotedContent(); | |
214 return; | |
215 } | |
216 | |
217 TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->ap
ply(); | |
218 } | |
219 | |
220 void TypingCommand::insertParagraphSeparator(Document& document, Options options
) | |
221 { | |
222 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(document.frame())) { | |
223 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & Reta
inAutocorrectionIndicator); | |
224 lastTypingCommand->insertParagraphSeparator(); | |
225 return; | |
226 } | |
227 | |
228 TypingCommand::create(document, InsertParagraphSeparator, "", options)->appl
y(); | |
229 } | |
230 | |
231 PassRefPtrWillBeRawPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpe
nForTyping(LocalFrame* frame) | |
232 { | |
233 ASSERT(frame); | |
234 | |
235 RefPtrWillBeRawPtr<CompositeEditCommand> lastEditCommand = frame->editor().l
astEditCommand(); | |
236 if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<
TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping()) | |
237 return nullptr; | |
238 | |
239 return static_cast<TypingCommand*>(lastEditCommand.get()); | |
240 } | |
241 | |
242 void TypingCommand::closeTyping(LocalFrame* frame) | |
243 { | |
244 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandI
fStillOpenForTyping(frame)) | |
245 lastTypingCommand->closeTyping(); | |
246 } | |
247 | |
248 void TypingCommand::doApply() | |
249 { | |
250 if (!endingSelection().isNonOrphanedCaretOrRange()) | |
251 return; | |
252 | |
253 if (m_commandType == DeleteKey) { | |
254 if (m_commands.isEmpty()) | |
255 m_openedByBackwardDelete = true; | |
256 } | |
257 | |
258 switch (m_commandType) { | |
259 case DeleteSelection: | |
260 deleteSelection(m_smartDelete); | |
261 return; | |
262 case DeleteKey: | |
263 deleteKeyPressed(m_granularity, m_killRing); | |
264 return; | |
265 case ForwardDeleteKey: | |
266 forwardDeleteKeyPressed(m_granularity, m_killRing); | |
267 return; | |
268 case InsertLineBreak: | |
269 insertLineBreak(); | |
270 return; | |
271 case InsertParagraphSeparator: | |
272 insertParagraphSeparator(); | |
273 return; | |
274 case InsertParagraphSeparatorInQuotedContent: | |
275 insertParagraphSeparatorInQuotedContent(); | |
276 return; | |
277 case InsertText: | |
278 insertText(m_textToInsert, m_selectInsertedText); | |
279 return; | |
280 } | |
281 | |
282 ASSERT_NOT_REACHED(); | |
283 } | |
284 | |
285 EditAction TypingCommand::editingAction() const | |
286 { | |
287 return EditActionTyping; | |
288 } | |
289 | |
290 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) | |
291 { | |
292 LocalFrame* frame = document().frame(); | |
293 if (!frame) | |
294 return; | |
295 | |
296 if (!frame->spellChecker().isContinuousSpellCheckingEnabled()) | |
297 return; | |
298 | |
299 frame->spellChecker().cancelCheck(); | |
300 | |
301 // Take a look at the selection that results after typing and determine whet
her we need to spellcheck. | |
302 // Since the word containing the current selection is never marked, this doe
s a check to | |
303 // see if typing made a new word that is not in the current selection. Basic
ally, you | |
304 // get this by being at the end of a word and typing a space. | |
305 VisiblePosition start(endingSelection().start(), endingSelection().affinity(
)); | |
306 VisiblePosition previous = start.previous(); | |
307 | |
308 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); | |
309 | |
310 if (commandType == InsertParagraphSeparator) { | |
311 VisiblePosition p2 = nextWordPosition(start); | |
312 VisibleSelection words(p1, endOfWord(p2)); | |
313 frame->spellChecker().markMisspellingsAfterLineBreak(words); | |
314 } else if (previous.isNotNull()) { | |
315 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); | |
316 if (p1.deepEquivalent() != p2.deepEquivalent()) | |
317 frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSe
lection()); | |
318 } | |
319 } | |
320 | |
321 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedT
yping) | |
322 { | |
323 LocalFrame* frame = document().frame(); | |
324 if (!frame) | |
325 return; | |
326 | |
327 updatePreservesTypingStyle(commandTypeForAddedTyping); | |
328 updateCommandTypeOfOpenCommand(commandTypeForAddedTyping); | |
329 | |
330 // The old spellchecking code requires that checking be done first, to preve
nt issues like that in 6864072, where <doesn't> is marked as misspelled. | |
331 markMisspellingsAfterTyping(commandTypeForAddedTyping); | |
332 frame->editor().appliedEditing(this); | |
333 } | |
334 | |
335 void TypingCommand::insertText(const String &text, bool selectInsertedText) | |
336 { | |
337 // FIXME: Need to implement selectInsertedText for cases where more than one
insert is involved. | |
338 // This requires support from insertTextRunWithoutNewlines and insertParagra
phSeparator for extending | |
339 // an existing selection; at the moment they can either put the caret after
what's inserted or | |
340 // select what's inserted, but there's no way to "extend selection" to inclu
de both an old selection | |
341 // that ends just before where we want to insert text and the newly inserted
text. | |
342 TypingCommandLineOperation operation(this, selectInsertedText, text); | |
343 forEachLineInString(text, operation); | |
344 } | |
345 | |
346 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool select
InsertedText) | |
347 { | |
348 RefPtrWillBeRawPtr<InsertTextCommand> command = InsertTextCommand::create(do
cument(), text, selectInsertedText, | |
349 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceL
eadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); | |
350 | |
351 applyCommandToComposite(command, endingSelection()); | |
352 | |
353 typingAddedToOpenCommand(InsertText); | |
354 } | |
355 | |
356 void TypingCommand::insertLineBreak() | |
357 { | |
358 if (!canAppendNewLineFeedToSelection(endingSelection())) | |
359 return; | |
360 | |
361 applyCommandToComposite(InsertLineBreakCommand::create(document())); | |
362 typingAddedToOpenCommand(InsertLineBreak); | |
363 } | |
364 | |
365 void TypingCommand::insertParagraphSeparator() | |
366 { | |
367 if (!canAppendNewLineFeedToSelection(endingSelection())) | |
368 return; | |
369 | |
370 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()))
; | |
371 typingAddedToOpenCommand(InsertParagraphSeparator); | |
372 } | |
373 | |
374 void TypingCommand::insertParagraphSeparatorInQuotedContent() | |
375 { | |
376 // If the selection starts inside a table, just insert the paragraph separat
or normally | |
377 // Breaking the blockquote would also break apart the table, which is uneces
sary when inserting a newline | |
378 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) { | |
379 insertParagraphSeparator(); | |
380 return; | |
381 } | |
382 | |
383 applyCommandToComposite(BreakBlockquoteCommand::create(document())); | |
384 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent); | |
385 } | |
386 | |
387 bool TypingCommand::makeEditableRootEmpty() | |
388 { | |
389 Element* root = endingSelection().rootEditableElement(); | |
390 if (!root || !root->hasChildren()) | |
391 return false; | |
392 | |
393 if (root->firstChild() == root->lastChild()) { | |
394 if (isHTMLBRElement(root->firstChild())) { | |
395 // If there is a single child and it could be a placeholder, leave i
t alone. | |
396 if (root->layoutObject() && root->layoutObject()->isLayoutBlockFlow(
)) | |
397 return false; | |
398 } | |
399 } | |
400 | |
401 while (Node* child = root->firstChild()) | |
402 removeNode(child); | |
403 | |
404 addBlockPlaceholderIfNeeded(root); | |
405 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, e
ndingSelection().isDirectional())); | |
406 | |
407 return true; | |
408 } | |
409 | |
410 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) | |
411 { | |
412 LocalFrame* frame = document().frame(); | |
413 if (!frame) | |
414 return; | |
415 | |
416 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); | |
417 | |
418 VisibleSelection selectionToDelete; | |
419 VisibleSelection selectionAfterUndo; | |
420 | |
421 switch (endingSelection().selectionType()) { | |
422 case RangeSelection: | |
423 selectionToDelete = endingSelection(); | |
424 selectionAfterUndo = selectionToDelete; | |
425 break; | |
426 case CaretSelection: { | |
427 // After breaking out of an empty mail blockquote, we still want continu
e with the deletion | |
428 // so actual content will get deleted, and not just the quote style. | |
429 if (breakOutOfEmptyMailBlockquotedParagraph()) | |
430 typingAddedToOpenCommand(DeleteKey); | |
431 | |
432 m_smartDelete = false; | |
433 | |
434 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create(); | |
435 selection->setSelection(endingSelection()); | |
436 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, g
ranularity); | |
437 if (killRing && selection->isCaret() && granularity != CharacterGranular
ity) | |
438 selection->modify(FrameSelection::AlterationExtend, DirectionBackwar
d, CharacterGranularity); | |
439 | |
440 VisiblePosition visibleStart(endingSelection().visibleStart()); | |
441 if (visibleStart.previous(CannotCrossEditingBoundary).isNull()) { | |
442 // When the caret is at the start of the editable area in an empty l
ist item, break out of the list item. | |
443 if (breakOutOfEmptyListItem()) { | |
444 typingAddedToOpenCommand(DeleteKey); | |
445 return; | |
446 } | |
447 // When there are no visible positions in the editing root, delete i
ts entire contents. | |
448 if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEd
itableRootEmpty()) { | |
449 typingAddedToOpenCommand(DeleteKey); | |
450 return; | |
451 } | |
452 } | |
453 | |
454 // If we have a caret selection at the beginning of a cell, we have noth
ing to do. | |
455 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivale
nt(), &isTableCell); | |
456 if (enclosingTableCell && visibleStart.deepEquivalent() == VisiblePositi
on(firstPositionInNode(enclosingTableCell)).deepEquivalent()) | |
457 return; | |
458 | |
459 // If the caret is at the start of a paragraph after a table, move conte
nt into the last table cell. | |
460 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibl
eStart.previous(CannotCrossEditingBoundary))) { | |
461 // Unless the caret is just before a table. We don't want to move a
table into the last table cell. | |
462 if (isLastPositionBeforeTable(visibleStart)) | |
463 return; | |
464 // Extend the selection backward into the last cell, then deletion w
ill handle the move. | |
465 selection->modify(FrameSelection::AlterationExtend, DirectionBackwar
d, granularity); | |
466 // If the caret is just after a table, select the table and don't delete
anything. | |
467 } else if (Element* table = isFirstPositionAfterTable(visibleStart)) { | |
468 setEndingSelection(VisibleSelection(positionBeforeNode(table), endin
gSelection().start(), DOWNSTREAM, endingSelection().isDirectional())); | |
469 typingAddedToOpenCommand(DeleteKey); | |
470 return; | |
471 } | |
472 | |
473 selectionToDelete = selection->selection(); | |
474 | |
475 if (granularity == CharacterGranularity && selectionToDelete.end().compu
teContainerNode() == selectionToDelete.start().computeContainerNode() | |
476 && selectionToDelete.end().computeOffsetInContainerNode() - selectio
nToDelete.start().computeOffsetInContainerNode() > 1) { | |
477 // If there are multiple Unicode code points to be deleted, adjust t
he range to match platform conventions. | |
478 selectionToDelete.setWithoutValidation(selectionToDelete.end(), sele
ctionToDelete.end().previous(BackwardDeletion)); | |
479 } | |
480 | |
481 if (!startingSelection().isRange() || selectionToDelete.base() != starti
ngSelection().start()) { | |
482 selectionAfterUndo = selectionToDelete; | |
483 } else { | |
484 // It's a little tricky to compute what the starting selection would
have been in the original document. | |
485 // We can't let the VisibleSelection class's validation kick in or i
t'll adjust for us based on | |
486 // the current state of the document and we'll get the wrong result. | |
487 selectionAfterUndo.setWithoutValidation(startingSelection().end(), s
electionToDelete.extent()); | |
488 } | |
489 break; | |
490 } | |
491 case NoSelection: | |
492 ASSERT_NOT_REACHED(); | |
493 break; | |
494 } | |
495 | |
496 ASSERT(!selectionToDelete.isNone()); | |
497 if (selectionToDelete.isNone()) | |
498 return; | |
499 | |
500 if (selectionToDelete.isCaret()) | |
501 return; | |
502 | |
503 if (killRing) | |
504 frame->editor().addToKillRing(selectionToDelete.toNormalizedEphemeralRan
ge()); | |
505 // On Mac, make undo select everything that has been deleted, unless an undo
will undo more than just this deletion. | |
506 // FIXME: This behaves like TextEdit except for the case where you open with
text insertion and then delete | |
507 // more text than you insert. In that case all of the text that was around
originally should be selected. | |
508 if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByB
ackwardDelete) | |
509 setStartingSelection(selectionAfterUndo); | |
510 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); | |
511 setSmartDelete(false); | |
512 typingAddedToOpenCommand(DeleteKey); | |
513 } | |
514 | |
515 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
llRing) | |
516 { | |
517 LocalFrame* frame = document().frame(); | |
518 if (!frame) | |
519 return; | |
520 | |
521 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); | |
522 | |
523 VisibleSelection selectionToDelete; | |
524 VisibleSelection selectionAfterUndo; | |
525 | |
526 switch (endingSelection().selectionType()) { | |
527 case RangeSelection: | |
528 selectionToDelete = endingSelection(); | |
529 selectionAfterUndo = selectionToDelete; | |
530 break; | |
531 case CaretSelection: { | |
532 m_smartDelete = false; | |
533 | |
534 // Handle delete at beginning-of-block case. | |
535 // Do nothing in the case that the caret is at the start of a | |
536 // root editable element or at the start of a document. | |
537 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create(); | |
538 selection->setSelection(endingSelection()); | |
539 selection->modify(FrameSelection::AlterationExtend, DirectionForward, gr
anularity); | |
540 if (killRing && selection->isCaret() && granularity != CharacterGranular
ity) | |
541 selection->modify(FrameSelection::AlterationExtend, DirectionForward
, CharacterGranularity); | |
542 | |
543 Position downstreamEnd = endingSelection().end().downstream(); | |
544 VisiblePosition visibleEnd = endingSelection().visibleEnd(); | |
545 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent
(), &isTableCell); | |
546 if (enclosingTableCell && visibleEnd.deepEquivalent() == VisiblePosition
(lastPositionInNode(enclosingTableCell)).deepEquivalent()) | |
547 return; | |
548 if (visibleEnd.deepEquivalent() == endOfParagraph(visibleEnd).deepEquiva
lent()) | |
549 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEqui
valent().downstream(); | |
550 // When deleting tables: Select the table first, then perform the deleti
on | |
551 if (isRenderedTableElement(downstreamEnd.computeContainerNode()) && down
streamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.compute
ContainerNode())) { | |
552 setEndingSelection(VisibleSelection(endingSelection().end(), positio
nAfterNode(downstreamEnd.computeContainerNode()), DOWNSTREAM, endingSelection().
isDirectional())); | |
553 typingAddedToOpenCommand(ForwardDeleteKey); | |
554 return; | |
555 } | |
556 | |
557 // deleting to end of paragraph when at end of paragraph needs to merge
the next paragraph (if any) | |
558 if (granularity == ParagraphBoundary && selection->selection().isCaret()
&& isEndOfParagraph(selection->selection().visibleEnd())) | |
559 selection->modify(FrameSelection::AlterationExtend, DirectionForward
, CharacterGranularity); | |
560 | |
561 selectionToDelete = selection->selection(); | |
562 if (!startingSelection().isRange() || selectionToDelete.base() != starti
ngSelection().start()) { | |
563 selectionAfterUndo = selectionToDelete; | |
564 } else { | |
565 // It's a little tricky to compute what the starting selection would
have been in the original document. | |
566 // We can't let the VisibleSelection class's validation kick in or i
t'll adjust for us based on | |
567 // the current state of the document and we'll get the wrong result. | |
568 Position extent = startingSelection().end(); | |
569 if (extent.computeContainerNode() != selectionToDelete.end().compute
ContainerNode()) { | |
570 extent = selectionToDelete.extent(); | |
571 } else { | |
572 int extraCharacters; | |
573 if (selectionToDelete.start().computeContainerNode() == selectio
nToDelete.end().computeContainerNode()) | |
574 extraCharacters = selectionToDelete.end().computeOffsetInCon
tainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); | |
575 else | |
576 extraCharacters = selectionToDelete.end().computeOffsetInCon
tainerNode(); | |
577 extent = Position(extent.computeContainerNode(), extent.computeO
ffsetInContainerNode() + extraCharacters); | |
578 } | |
579 selectionAfterUndo.setWithoutValidation(startingSelection().start(),
extent); | |
580 } | |
581 break; | |
582 } | |
583 case NoSelection: | |
584 ASSERT_NOT_REACHED(); | |
585 break; | |
586 } | |
587 | |
588 ASSERT(!selectionToDelete.isNone()); | |
589 if (selectionToDelete.isNone()) | |
590 return; | |
591 | |
592 if (selectionToDelete.isCaret()) | |
593 return; | |
594 | |
595 if (killRing) | |
596 frame->editor().addToKillRing(selectionToDelete.toNormalizedEphemeralRan
ge()); | |
597 // Make undo select what was deleted on Mac alone | |
598 if (frame->editor().behavior().shouldUndoOfDeleteSelectText()) | |
599 setStartingSelection(selectionAfterUndo); | |
600 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); | |
601 setSmartDelete(false); | |
602 typingAddedToOpenCommand(ForwardDeleteKey); | |
603 } | |
604 | |
605 void TypingCommand::deleteSelection(bool smartDelete) | |
606 { | |
607 CompositeEditCommand::deleteSelection(smartDelete); | |
608 typingAddedToOpenCommand(DeleteSelection); | |
609 } | |
610 | |
611 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType) | |
612 { | |
613 switch (commandType) { | |
614 case DeleteSelection: | |
615 case DeleteKey: | |
616 case ForwardDeleteKey: | |
617 case InsertParagraphSeparator: | |
618 case InsertLineBreak: | |
619 m_preservesTypingStyle = true; | |
620 return; | |
621 case InsertParagraphSeparatorInQuotedContent: | |
622 case InsertText: | |
623 m_preservesTypingStyle = false; | |
624 return; | |
625 } | |
626 ASSERT_NOT_REACHED(); | |
627 m_preservesTypingStyle = false; | |
628 } | |
629 | |
630 bool TypingCommand::isTypingCommand() const | |
631 { | |
632 return true; | |
633 } | |
634 | |
635 } // namespace blink | |
OLD | NEW |