| 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 |