Reviewed by John.
- split remaining editing command classes out of htmlediting.cpp
- rename InsertParagraphSeparatorInQuotedContentCommand to BreakBlockquoteCommand
No layout tests needed - this is a pure refactoring change.
* WebCore.pbproj/project.pbxproj:
* khtml/editing/apply_style_command.cpp:
* khtml/editing/break_blockquote_command.cpp: Added.
* khtml/editing/break_blockquote_command.h: Added.
* khtml/editing/composite_edit_command.cpp:
* khtml/editing/delete_selection_command.cpp:
* khtml/editing/edit_command.cpp:
* khtml/editing/htmlediting.cpp:
* khtml/editing/htmlediting.h:
* khtml/editing/insert_line_break_command.cpp: Added.
* khtml/editing/insert_line_break_command.h: Added.
* khtml/editing/insert_paragraph_separator_command.cpp: Added.
* khtml/editing/insert_paragraph_separator_command.h: Added.
* khtml/editing/insert_text_command.cpp: Added.
* khtml/editing/insert_text_command.h: Added.
* khtml/editing/join_text_nodes_command.cpp: Added.
* khtml/editing/join_text_nodes_command.h: Added.
* khtml/editing/merge_identical_elements_command.cpp: Added.
* khtml/editing/merge_identical_elements_command.h: Added.
* khtml/editing/move_selection_command.cpp: Added.
* khtml/editing/move_selection_command.h: Added.
* khtml/editing/rebalance_whitespace_command.cpp: Added.
* khtml/editing/rebalance_whitespace_command.h: Added.
* khtml/editing/remove_css_property_command.cpp: Added.
* khtml/editing/remove_css_property_command.h: Added.
* khtml/editing/remove_node_attribute_command.cpp: Added.
* khtml/editing/remove_node_attribute_command.h: Added.
* khtml/editing/remove_node_command.cpp: Added.
* khtml/editing/remove_node_command.h: Added.
* khtml/editing/remove_node_preserving_children_command.cpp: Added.
* khtml/editing/remove_node_preserving_children_command.h: Added.
* khtml/editing/replace_selection_command.cpp: Added.
* khtml/editing/replace_selection_command.h: Added.
* khtml/editing/set_node_attribute_command.cpp: Added.
* khtml/editing/set_node_attribute_command.h: Added.
* khtml/editing/split_element_command.cpp: Added.
* khtml/editing/split_element_command.h: Added.
* khtml/editing/split_text_node_command.cpp: Added.
* khtml/editing/split_text_node_command.h: Added.
* khtml/editing/split_text_node_containing_element_command.h: Added.
* khtml/editing/typing_command.cpp: Added.
* khtml/editing/typing_command.h: Added.
* khtml/editing/wrap_contents_in_dummy_span_command.cpp: Added.
* khtml/editing/wrap_contents_in_dummy_span_command.h: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@9196 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/WebCore/khtml/editing/typing_command.cpp b/WebCore/khtml/editing/typing_command.cpp
new file mode 100644
index 0000000..216b56b
--- /dev/null
+++ b/WebCore/khtml/editing/typing_command.cpp
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "typing_command.h"
+
+#include "insert_text_command.h"
+#include "insert_line_break_command.h"
+#include "insert_paragraph_separator_command.h"
+#include "break_blockquote_command.h"
+
+#include "khtml_part.h"
+#include "xml/dom_docimpl.h"
+#include "visible_position.h"
+#include "visible_units.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::DOMString;
+using DOM::Position;
+
+namespace khtml {
+
+TypingCommand::TypingCommand(DocumentImpl *document, ETypingCommand commandType, const DOMString &textToInsert, bool selectInsertedText)
+ : CompositeEditCommand(document),
+ m_commandType(commandType),
+ m_textToInsert(textToInsert),
+ m_openForMoreTyping(true),
+ m_applyEditing(false),
+ m_selectInsertedText(selectInsertedText),
+ m_smartDelete(false)
+{
+}
+
+void TypingCommand::deleteKeyPressed(DocumentImpl *document, bool smartDelete)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->deleteKeyPressed();
+ }
+ else {
+ Selection selection = part->selection();
+ if (selection.isCaret() && VisiblePosition(selection.start(), selection.startAffinity()).previous().isNull()) {
+ // do nothing for a delete key at the start of an editable element.
+ }
+ else {
+ TypingCommand *typingCommand = new TypingCommand(document, DeleteKey);
+ typingCommand->setSmartDelete(smartDelete);
+ EditCommandPtr cmd(typingCommand);
+ cmd.apply();
+ }
+ }
+}
+
+void TypingCommand::forwardDeleteKeyPressed(DocumentImpl *document, bool smartDelete)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->forwardDeleteKeyPressed();
+ }
+ else {
+ Selection selection = part->selection();
+ if (selection.isCaret() && isEndOfDocument(VisiblePosition(selection.start(), selection.startAffinity()))) {
+ // do nothing for a delete key at the start of an editable element.
+ }
+ else {
+ TypingCommand *typingCommand = new TypingCommand(document, ForwardDeleteKey);
+ typingCommand->setSmartDelete(smartDelete);
+ EditCommandPtr cmd(typingCommand);
+ cmd.apply();
+ }
+ }
+}
+
+void TypingCommand::insertText(DocumentImpl *document, const DOMString &text, bool selectInsertedText)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertText(text, selectInsertedText);
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertText, text, selectInsertedText));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertLineBreak(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertLineBreak();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertLineBreak));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertParagraphSeparatorInQuotedContent(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparatorInQuotedContent();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparatorInQuotedContent));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertParagraphSeparator(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparator();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparator));
+ cmd.apply();
+ }
+}
+
+bool TypingCommand::isOpenForMoreTypingCommand(const EditCommandPtr &cmd)
+{
+ return cmd.isTypingCommand() &&
+ static_cast<const TypingCommand *>(cmd.get())->openForMoreTyping();
+}
+
+void TypingCommand::closeTyping(const EditCommandPtr &cmd)
+{
+ if (isOpenForMoreTypingCommand(cmd))
+ static_cast<TypingCommand *>(cmd.get())->closeTyping();
+}
+
+void TypingCommand::doApply()
+{
+ if (endingSelection().isNone())
+ return;
+
+ switch (m_commandType) {
+ case DeleteKey:
+ deleteKeyPressed();
+ return;
+ case ForwardDeleteKey:
+ forwardDeleteKeyPressed();
+ return;
+ case InsertLineBreak:
+ insertLineBreak();
+ return;
+ case InsertParagraphSeparator:
+ insertParagraphSeparator();
+ return;
+ case InsertParagraphSeparatorInQuotedContent:
+ insertParagraphSeparatorInQuotedContent();
+ return;
+ case InsertText:
+ insertText(m_textToInsert, m_selectInsertedText);
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+EditAction TypingCommand::editingAction() const
+{
+ return EditActionTyping;
+}
+
+void TypingCommand::markMisspellingsAfterTyping()
+{
+ // Take a look at the selection that results after typing and determine whether we need to spellcheck.
+ // Since the word containing the current selection is never marked, this does a check to
+ // see if typing made a new word that is not in the current selection. Basically, you
+ // get this by being at the end of a word and typing a space.
+ VisiblePosition start(endingSelection().start(), endingSelection().startAffinity());
+ VisiblePosition previous = start.previous();
+ if (previous.isNotNull()) {
+ VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
+ VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
+ if (p1 != p2)
+ KWQ(document()->part())->markMisspellingsInAdjacentWords(p1);
+ }
+}
+
+void TypingCommand::typingAddedToOpenCommand()
+{
+ markMisspellingsAfterTyping();
+ // Do not apply editing to the part on the first time through.
+ // The part will get told in the same way as all other commands.
+ // But since this command stays open and is used for additional typing,
+ // we need to tell the part here as other commands are added.
+ if (m_applyEditing) {
+ EditCommandPtr cmd(this);
+ document()->part()->appliedEditing(cmd);
+ }
+ m_applyEditing = true;
+}
+
+void TypingCommand::insertText(const DOMString &text, bool selectInsertedText)
+{
+ // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
+ // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
+ // an existing selection; at the moment they can either put the caret after what's inserted or
+ // select what's inserted, but there's no way to "extend selection" to include both an old selection
+ // that ends just before where we want to insert text and the newly inserted text.
+ int offset = 0;
+ int newline;
+ while ((newline = text.find('\n', offset)) != -1) {
+ if (newline != offset) {
+ insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
+ }
+ insertParagraphSeparator();
+ offset = newline + 1;
+ }
+ if (offset == 0) {
+ insertTextRunWithoutNewlines(text, selectInsertedText);
+ } else {
+ int length = text.length();
+ if (length != offset) {
+ insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
+ }
+ }
+}
+
+void TypingCommand::insertTextRunWithoutNewlines(const DOMString &text, bool selectInsertedText)
+{
+ // FIXME: Improve typing style.
+ // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+ if (document()->part()->typingStyle() || m_cmds.count() == 0) {
+ InsertTextCommand *impl = new InsertTextCommand(document());
+ EditCommandPtr cmd(impl);
+ applyCommandToComposite(cmd);
+ impl->input(text, selectInsertedText);
+ }
+ else {
+ EditCommandPtr lastCommand = m_cmds.last();
+ if (lastCommand.isInsertTextCommand()) {
+ InsertTextCommand *impl = static_cast<InsertTextCommand *>(lastCommand.get());
+ impl->input(text, selectInsertedText);
+ }
+ else {
+ InsertTextCommand *impl = new InsertTextCommand(document());
+ EditCommandPtr cmd(impl);
+ applyCommandToComposite(cmd);
+ impl->input(text, selectInsertedText);
+ }
+ }
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertLineBreak()
+{
+ EditCommandPtr cmd(new InsertLineBreakCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertParagraphSeparator()
+{
+ EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertParagraphSeparatorInQuotedContent()
+{
+ EditCommandPtr cmd(new BreakBlockquoteCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::deleteKeyPressed()
+{
+ Selection selectionToDelete;
+
+ switch (endingSelection().state()) {
+ case Selection::RANGE:
+ selectionToDelete = endingSelection();
+ break;
+ case Selection::CARET: {
+ // Handle delete at beginning-of-block case.
+ // Do nothing in the case that the caret is at the start of a
+ // root editable element or at the start of a document.
+ Position pos(endingSelection().start());
+ Position start = VisiblePosition(pos, endingSelection().startAffinity()).previous().deepEquivalent();
+ Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
+ if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
+ selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
+ break;
+ }
+ case Selection::NONE:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ if (selectionToDelete.isCaretOrRange()) {
+ deleteSelection(selectionToDelete, m_smartDelete);
+ setSmartDelete(false);
+ typingAddedToOpenCommand();
+ }
+}
+
+void TypingCommand::forwardDeleteKeyPressed()
+{
+ Selection selectionToDelete;
+
+ switch (endingSelection().state()) {
+ case Selection::RANGE:
+ selectionToDelete = endingSelection();
+ break;
+ case Selection::CARET: {
+ // Handle delete at beginning-of-block case.
+ // Do nothing in the case that the caret is at the start of a
+ // root editable element or at the start of a document.
+ Position pos(endingSelection().start());
+ Position start = VisiblePosition(pos, endingSelection().startAffinity()).next().deepEquivalent();
+ Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
+ if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
+ selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
+ break;
+ }
+ case Selection::NONE:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ if (selectionToDelete.isCaretOrRange()) {
+ deleteSelection(selectionToDelete, m_smartDelete);
+ setSmartDelete(false);
+ typingAddedToOpenCommand();
+ }
+}
+
+bool TypingCommand::preservesTypingStyle() const
+{
+ switch (m_commandType) {
+ case DeleteKey:
+ case ForwardDeleteKey:
+ case InsertParagraphSeparator:
+ case InsertLineBreak:
+ return true;
+ case InsertParagraphSeparatorInQuotedContent:
+ case InsertText:
+ return false;
+ }
+ ASSERT_NOT_REACHED();
+ return false;
+}
+
+bool TypingCommand::isTypingCommand() const
+{
+ return true;
+}
+
+} // namespace khtml