2010-10-01 Ryosuke Niwa <rniwa@webkit.org>
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Oct 2010 04:49:45 +0000 (04:49 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Oct 2010 04:49:45 +0000 (04:49 +0000)
        Reviewed by Darin Adler.

        FormatBlockCommand and IndentOutdentCommand should use the same code to iterate paragraphs
        https://bugs.webkit.org/show_bug.cgi?id=46840

        Added ApplyBlockElementCommand, which is an abstract class inherited by FormatBlockCommand
        and IndentOutdentCommand.  It is intended to be inherited by InsertListCommand as well.

        ApplyBlockElementCommand's doApply verifies the current selection and exits early
        if it's invalid or orphaned or if the current selection is outside editable region.
        It then calls formatSelection to apply the block element after which doApply restores the selection.
        formatSelection iterates through paragraphs and calls formatParagraph, a pure virtual function
        implemented by FormatBlockCommand and IndentOutdentCommand, on each paragraph.

        No new tests are added since this is a refactoring.

        * Android.mk: Added ApplyBlockElementCommand.cpp.
        * CMakeLists.txt: Ditto.
        * GNUmakefile.am: Added ApplyBlockElementCommand.cpp and ApplyBlockElementCommand.h.
        * WebCore.gypi: Ditto.
        * WebCore.pro: Ditto.
        * WebCore.xcodeproj/project.pbxproj: Ditto.
        * editing/ApplyBlockElementCommand.cpp: Added.
        (WebCore::countParagraphs): Moved from IndentOutdentCommand.cpp
        (WebCore::ApplyBlockElementCommand::ApplyBlockElementCommand): Added.
        (WebCore::ApplyBlockElementCommand::doApply): Moved from IndentOutdentCommand::doApply.
        (WebCore::ApplyBlockElementCommand::formatSelection): Moved from IndentOutdentCommand::indentRegion.
        (WebCore::ApplyBlockElementCommand::createBlockElement): Added.
        (WebCore::ApplyBlockElementCommand::splitTextNodes): Moved from IndentOutdentCommand::splitTextNodes.
        * editing/ApplyBlockElementCommand.h: Added.
        * editing/EditingAllInOne.cpp:
        * editing/EditorCommand.cpp:
        (WebCore::executeFormatBlock):
        * editing/FormatBlockCommand.cpp: Removed doApply.
        (WebCore::FormatBlockCommand::FormatBlockCommand): Calls ApplyBlockElementCommand's constructor.
        (WebCore::FormatBlockCommand::formatParagraph): Renamed from doApplyForSingleParagraph.
        * editing/FormatBlockCommand.h: FormatBlockCommand inherits from ApplyBlockElementCommand.
        (WebCore::FormatBlockCommand::create): Uses QualifiedName for the tag name instead of AtomicString.
        * editing/IndentOutdentCommand.cpp: Removed doApply, indentIntoBlockquote, and splitTextNodes.
        (WebCore::IndentOutdentCommand::IndentOutdentCommand): Calls ApplyBlockElementCommand's constructor.
        (WebCore::IndentOutdentCommand::indentIntoBlockquote): The code to nullify targetBlockquote
        when the next paragraph is in a different table cell is moved to ApplyBlockElementCommand::formatSelection.
        (WebCore::IndentOutdentCommand::formatSelection): Added. Calls outdentRegion when outdenting.
        (WebCore::IndentOutdentCommand::formatParagraph): Added. Calls tryIndentingAsListItem and indentIntoBlockquote.
        * editing/IndentOutdentCommand.h: IndentOutdentCommand inherits from ApplyBlockElementCommand.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@68972 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
WebCore/Android.mk
WebCore/CMakeLists.txt
WebCore/ChangeLog
WebCore/GNUmakefile.am
WebCore/WebCore.gypi
WebCore/WebCore.pro
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/editing/ApplyBlockElementCommand.cpp [new file with mode: 0644]
WebCore/editing/ApplyBlockElementCommand.h [new file with mode: 0644]
WebCore/editing/EditingAllInOne.cpp
WebCore/editing/EditorCommand.cpp
WebCore/editing/FormatBlockCommand.cpp
WebCore/editing/FormatBlockCommand.h
WebCore/editing/IndentOutdentCommand.cpp
WebCore/editing/IndentOutdentCommand.h

index f9dc842..f96df00 100644 (file)
@@ -198,6 +198,7 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
        dom/default/PlatformMessagePortChannel.cpp \
        \
        editing/AppendNodeCommand.cpp \
+       editing/ApplyBlockElementCommand.cpp \
        editing/ApplyStyleCommand.cpp \
        editing/BreakBlockquoteCommand.cpp \
        editing/CompositeEditCommand.cpp \
index 764cfbb..553145f 100644 (file)
@@ -882,6 +882,7 @@ SET(WebCore_SOURCES
     dom/default/PlatformMessagePortChannel.cpp
 
     editing/AppendNodeCommand.cpp
+    editing/ApplyBlockElementCommand.cpp
     editing/ApplyStyleCommand.cpp
     editing/BreakBlockquoteCommand.cpp
     editing/CompositeEditCommand.cpp
index 0148063..b796973 100644 (file)
@@ -1,3 +1,51 @@
+2010-10-01  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Darin Adler.
+
+        FormatBlockCommand and IndentOutdentCommand should use the same code to iterate paragraphs
+        https://bugs.webkit.org/show_bug.cgi?id=46840
+
+        Added ApplyBlockElementCommand, which is an abstract class inherited by FormatBlockCommand
+        and IndentOutdentCommand.  It is intended to be inherited by InsertListCommand as well.
+
+        ApplyBlockElementCommand's doApply verifies the current selection and exits early
+        if it's invalid or orphaned or if the current selection is outside editable region.
+        It then calls formatSelection to apply the block element after which doApply restores the selection.
+        formatSelection iterates through paragraphs and calls formatParagraph, a pure virtual function
+        implemented by FormatBlockCommand and IndentOutdentCommand, on each paragraph.
+
+        No new tests are added since this is a refactoring.
+
+        * Android.mk: Added ApplyBlockElementCommand.cpp.
+        * CMakeLists.txt: Ditto.
+        * GNUmakefile.am: Added ApplyBlockElementCommand.cpp and ApplyBlockElementCommand.h.
+        * WebCore.gypi: Ditto.
+        * WebCore.pro: Ditto.
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * editing/ApplyBlockElementCommand.cpp: Added.
+        (WebCore::countParagraphs): Moved from IndentOutdentCommand.cpp
+        (WebCore::ApplyBlockElementCommand::ApplyBlockElementCommand): Added.
+        (WebCore::ApplyBlockElementCommand::doApply): Moved from IndentOutdentCommand::doApply.
+        (WebCore::ApplyBlockElementCommand::formatSelection): Moved from IndentOutdentCommand::indentRegion.
+        (WebCore::ApplyBlockElementCommand::createBlockElement): Added.
+        (WebCore::ApplyBlockElementCommand::splitTextNodes): Moved from IndentOutdentCommand::splitTextNodes.
+        * editing/ApplyBlockElementCommand.h: Added.
+        * editing/EditingAllInOne.cpp:
+        * editing/EditorCommand.cpp:
+        (WebCore::executeFormatBlock):
+        * editing/FormatBlockCommand.cpp: Removed doApply.
+        (WebCore::FormatBlockCommand::FormatBlockCommand): Calls ApplyBlockElementCommand's constructor.
+        (WebCore::FormatBlockCommand::formatParagraph): Renamed from doApplyForSingleParagraph.
+        * editing/FormatBlockCommand.h: FormatBlockCommand inherits from ApplyBlockElementCommand.
+        (WebCore::FormatBlockCommand::create): Uses QualifiedName for the tag name instead of AtomicString.
+        * editing/IndentOutdentCommand.cpp: Removed doApply, indentIntoBlockquote, and splitTextNodes.
+        (WebCore::IndentOutdentCommand::IndentOutdentCommand): Calls ApplyBlockElementCommand's constructor.
+        (WebCore::IndentOutdentCommand::indentIntoBlockquote): The code to nullify targetBlockquote
+        when the next paragraph is in a different table cell is moved to ApplyBlockElementCommand::formatSelection.
+        (WebCore::IndentOutdentCommand::formatSelection): Added. Calls outdentRegion when outdenting.
+        (WebCore::IndentOutdentCommand::formatParagraph): Added. Calls tryIndentingAsListItem and indentIntoBlockquote.
+        * editing/IndentOutdentCommand.h: IndentOutdentCommand inherits from ApplyBlockElementCommand.
+
 2010-10-01  Mark Rowe  <mrowe@apple.com>
 
         Build fix.
index a743983..8033c72 100644 (file)
@@ -1256,6 +1256,8 @@ webcore_sources += \
        WebCore/dom/XMLDocumentParserScope.h \
        WebCore/editing/AppendNodeCommand.cpp \
        WebCore/editing/AppendNodeCommand.h \
+       WebCore/editing/ApplyBlockElementCommand.cpp \
+       WebCore/editing/ApplyBlockElementCommand.h \
        WebCore/editing/ApplyStyleCommand.cpp \
        WebCore/editing/ApplyStyleCommand.h \
        WebCore/editing/BreakBlockquoteCommand.cpp \
index e9aef7b..05807da 100644 (file)
             'editing/wx/EditorWx.cpp',
             'editing/AppendNodeCommand.cpp',
             'editing/AppendNodeCommand.h',
+            'editing/ApplyBlockElementCommand.cpp',
+            'editing/ApplyBlockElementCommand.h',
             'editing/ApplyStyleCommand.cpp',
             'editing/ApplyStyleCommand.h',
             'editing/BreakBlockquoteCommand.cpp',
index a2635be..6ce9f16 100644 (file)
@@ -772,6 +772,7 @@ SOURCES += \
     dom/XMLDocumentParserQt.cpp \
     dom/default/PlatformMessagePortChannel.cpp \
     editing/AppendNodeCommand.cpp \
+    editing/ApplyBlockElementCommand.cpp \
     editing/ApplyStyleCommand.cpp \
     editing/BreakBlockquoteCommand.cpp \
     editing/CompositeEditCommand.cpp \
@@ -1668,6 +1669,7 @@ HEADERS += \
     dom/WheelEvent.h \
     dom/XMLDocumentParser.h \
     editing/AppendNodeCommand.h \
+    editing/ApplyBlockElementCommand.h \
     editing/ApplyStyleCommand.h \
     editing/BreakBlockquoteCommand.h \
     editing/CompositeEditCommand.h \
index 33ab05b..5433703 100644 (file)
                97DD4D860FDF4D6E00ECF9A4 /* XSSAuditor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97DD4D840FDF4D6D00ECF9A4 /* XSSAuditor.cpp */; };
                97DD4D870FDF4D6E00ECF9A4 /* XSSAuditor.h in Headers */ = {isa = PBXBuildFile; fileRef = 97DD4D850FDF4D6E00ECF9A4 /* XSSAuditor.h */; };
                97EF7DFE107E55B700D7C49C /* ScriptControllerBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97EF7DFD107E55B700D7C49C /* ScriptControllerBase.cpp */; };
+               9B417064125662B3006B28FC /* ApplyBlockElementCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B417062125662B3006B28FC /* ApplyBlockElementCommand.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B417065125662B3006B28FC /* ApplyBlockElementCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */; };
                9F0D6B2E121BFEBA006C0288 /* InspectorProfilerAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9F0D6B2C121BFEBA006C0288 /* InspectorProfilerAgent.cpp */; };
                9F0D6B2F121BFEBA006C0288 /* InspectorProfilerAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F0D6B2D121BFEBA006C0288 /* InspectorProfilerAgent.h */; };
                9F3B947E12241758005304E7 /* ScriptHeapSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F3B947D12241758005304E7 /* ScriptHeapSnapshot.h */; };
                97DD4D840FDF4D6D00ECF9A4 /* XSSAuditor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XSSAuditor.cpp; sourceTree = "<group>"; };
                97DD4D850FDF4D6E00ECF9A4 /* XSSAuditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XSSAuditor.h; sourceTree = "<group>"; };
                97EF7DFD107E55B700D7C49C /* ScriptControllerBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptControllerBase.cpp; sourceTree = "<group>"; };
+               9B417062125662B3006B28FC /* ApplyBlockElementCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplyBlockElementCommand.h; sourceTree = "<group>"; };
+               9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApplyBlockElementCommand.cpp; sourceTree = "<group>"; };
                9F0D6B2C121BFEBA006C0288 /* InspectorProfilerAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorProfilerAgent.cpp; sourceTree = "<group>"; };
                9F0D6B2D121BFEBA006C0288 /* InspectorProfilerAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorProfilerAgent.h; sourceTree = "<group>"; };
                9F3B947D12241758005304E7 /* ScriptHeapSnapshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptHeapSnapshot.h; sourceTree = "<group>"; };
                                ED501DC90B249F3900AE18D9 /* mac */,
                                93309D87099E64910056E581 /* AppendNodeCommand.cpp */,
                                93309D88099E64910056E581 /* AppendNodeCommand.h */,
+                               9B417062125662B3006B28FC /* ApplyBlockElementCommand.h */,
+                               9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */,
                                93309D89099E64910056E581 /* ApplyStyleCommand.cpp */,
                                93309D8A099E64910056E581 /* ApplyStyleCommand.h */,
                                93309D8B099E64910056E581 /* BreakBlockquoteCommand.cpp */,
                                24F54EAD101FE914000AE741 /* ApplicationCacheHost.h in Headers */,
                                1A8F6BC10DB55CDC001DB794 /* ApplicationCacheResource.h in Headers */,
                                1A2AAC590DC2A3B100A20D9A /* ApplicationCacheStorage.h in Headers */,
+                               9B417064125662B3006B28FC /* ApplyBlockElementCommand.h in Headers */,
                                93309DD9099E64920056E581 /* ApplyStyleCommand.h in Headers */,
                                512DD8F60D91E6AF000F89EE /* Archive.h in Headers */,
                                512DD8FD0D91E6AF000F89EE /* ArchiveFactory.h in Headers */,
                        isa = PBXProject;
                        buildConfigurationList = 149C284308902B11008A9EFC /* Build configuration list for PBXProject "WebCore" */;
                        compatibilityVersion = "Xcode 2.4";
-                       developmentRegion = English;
                        hasScannedForEncodings = 1;
                        knownRegions = (
                                English,
                                24F54EAC101FE914000AE741 /* ApplicationCacheHost.cpp in Sources */,
                                1A8F6BC00DB55CDC001DB794 /* ApplicationCacheResource.cpp in Sources */,
                                1A2AAC580DC2A3B100A20D9A /* ApplicationCacheStorage.cpp in Sources */,
+                               9B417065125662B3006B28FC /* ApplyBlockElementCommand.cpp in Sources */,
                                93309DD8099E64920056E581 /* ApplyStyleCommand.cpp in Sources */,
                                512DD8FA0D91E6AF000F89EE /* ArchiveFactory.cpp in Sources */,
                                512DD8FB0D91E6AF000F89EE /* ArchiveResource.cpp in Sources */,
diff --git a/WebCore/editing/ApplyBlockElementCommand.cpp b/WebCore/editing/ApplyBlockElementCommand.cpp
new file mode 100644 (file)
index 0000000..b2ce6f4
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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 "config.h"
+#include "ApplyBlockElementCommand.h"
+
+#include "HTMLElement.h"
+#include "HTMLNames.h"
+#include "Text.h"
+#include "TextIterator.h"
+#include "VisiblePosition.h"
+#include "htmlediting.h"
+#include "visible_units.h"
+
+namespace WebCore {
+
+using namespace HTMLNames;
+    
+// This function can return -1 if we are unable to count the paragraphs between |start| and |end|.
+static int countParagraphs(const VisiblePosition& endOfFirstParagraph, const VisiblePosition& endOfLastParagraph)
+{
+    int count = 0;
+    VisiblePosition cur = endOfFirstParagraph;
+    while (cur != endOfLastParagraph) {
+        ++count;
+        cur = endOfParagraph(cur.next());
+        // If start is before a table and end is inside a table, we will never hit end because the
+        // whole table is considered a single paragraph.
+        if (cur.isNull())
+            return -1;
+    }
+    return count;
+}
+
+ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName, const AtomicString& className, const AtomicString& inlineStyle)
+    : CompositeEditCommand(document)
+    , m_tagName(tagName)
+    , m_className(className)
+    , m_inlineStyle(inlineStyle)
+{
+}
+
+ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName)
+    : CompositeEditCommand(document)
+    , m_tagName(tagName)
+{
+}
+
+void ApplyBlockElementCommand::doApply()
+{
+    if (!endingSelection().isNonOrphanedCaretOrRange())
+        return;
+
+    if (!endingSelection().rootEditableElement())
+        return;
+
+    VisiblePosition visibleEnd = endingSelection().visibleEnd();
+    VisiblePosition visibleStart = endingSelection().visibleStart();
+    // When a selection ends at the start of a paragraph, we rarely paint 
+    // the selection gap before that paragraph, because there often is no gap.  
+    // In a case like this, it's not obvious to the user that the selection 
+    // ends "inside" that paragraph, so it would be confusing if Indent/Outdent 
+    // operated on that paragraph.
+    // FIXME: We paint the gap before some paragraphs that are indented with left 
+    // margin/padding, but not others.  We should make the gap painting more consistent and 
+    // then use a left margin/padding rule here.
+    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
+        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));
+
+    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
+    VisiblePosition startOfSelection = selection.visibleStart();
+    VisiblePosition endOfSelection = selection.visibleEnd();
+    ASSERT(!startOfSelection.isNull());
+    ASSERT(!endOfSelection.isNull());
+    int startIndex = indexForVisiblePosition(startOfSelection);
+    int endIndex = indexForVisiblePosition(endOfSelection);
+
+    formatSelection(startOfSelection, endOfSelection);
+
+    updateLayout();
+
+    RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
+    RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
+    if (startRange && endRange)
+        setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
+}
+
+void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
+{
+    // Special case empty unsplittable elements because there's nothing to split
+    // and there's nothing to move.
+    Position start = startOfSelection.deepEquivalent().downstream();
+    if (isAtUnsplittableElement(start)) {
+        RefPtr<Element> blockquote = createBlockElement();
+        insertNodeAt(blockquote, start);
+        RefPtr<Element> placeholder = createBreakElement(document());
+        appendNode(placeholder, blockquote);
+        setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
+        return;
+    }
+
+    RefPtr<Element> blockquoteForNextIndent;
+    VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
+    VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
+    int endOfCurrentParagraphIndex = indexForVisiblePosition(endOfCurrentParagraph);
+    int endAfterSelectionIndex = indexForVisiblePosition(endAfterSelection);
+
+    // When indenting within a <pre> tag, we need to split each paragraph into a separate node for moveParagraphWithClones to work.
+    // However, splitting text nodes can cause endOfCurrentParagraph and endAfterSelection to point to an invalid position if we
+    // changed the text node it was pointing at.  So we have to reset these positions.
+    int numParagraphs = countParagraphs(endOfCurrentParagraph, endAfterSelection);
+    if (splitTextNodes(startOfParagraph(startOfSelection), numParagraphs + 1)) {
+        RefPtr<Range> endOfCurrentParagraphRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endOfCurrentParagraphIndex, 0, true);
+        RefPtr<Range> endAfterSelectionRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endAfterSelectionIndex, 0, true);
+        if (!endOfCurrentParagraphRange.get() || !endAfterSelectionRange.get()) {
+            ASSERT_NOT_REACHED();
+            return;
+        }
+        endOfCurrentParagraph = VisiblePosition(endOfCurrentParagraphRange->startPosition(), DOWNSTREAM);
+        endAfterSelection = VisiblePosition(endAfterSelectionRange->startPosition(), DOWNSTREAM);
+    }
+
+    while (endOfCurrentParagraph != endAfterSelection) {
+        // Iterate across the selected paragraphs...
+        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+        Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
+
+        formatParagraph(endOfCurrentParagraph, blockquoteForNextIndent);
+
+        // Don't put the next paragraph in the blockquote we just created for this paragraph unless 
+        // the next paragraph is in the same cell.
+        if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
+            blockquoteForNextIndent = 0;
+
+        // indentIntoBlockquote could move more than one paragraph if the paragraph
+        // is in a list item or a table. As a result, endAfterSelection could refer to a position
+        // no longer in the document.
+        if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().node()->inDocument())
+            break;
+        // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node()
+        // If somehow we did, return to prevent crashes.
+        if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) {
+            ASSERT_NOT_REACHED();
+            return;
+        }
+        endOfCurrentParagraph = endOfNextParagraph;
+    }
+}
+
+PassRefPtr<Element> ApplyBlockElementCommand::createBlockElement() const
+{
+    RefPtr<Element> element = createHTMLElement(document(), m_tagName);
+    if (m_className.length())
+        element->setAttribute(classAttr, m_className);
+    if (m_inlineStyle.length())
+        element->setAttribute(styleAttr, m_inlineStyle);
+    return element.release();
+}
+
+// Returns true if at least one text node was split.
+bool ApplyBlockElementCommand::splitTextNodes(const VisiblePosition& start, int numParagraphs)
+{
+    VisiblePosition currentParagraphStart = start;
+    bool hasSplit = false;
+    int paragraphCount;
+    for (paragraphCount = 0; paragraphCount < numParagraphs; ++paragraphCount) {
+        // If there are multiple paragraphs in a single text node, we split the text node into a separate node for each paragraph.
+        if (currentParagraphStart.deepEquivalent().node()->isTextNode() && currentParagraphStart.deepEquivalent().node() == startOfParagraph(currentParagraphStart.previous()).deepEquivalent().node()) {
+            Text* textNode = static_cast<Text *>(currentParagraphStart.deepEquivalent().node());
+            int offset = currentParagraphStart.deepEquivalent().offsetInContainerNode();
+            splitTextNode(textNode, offset);
+            currentParagraphStart = VisiblePosition(textNode, 0, VP_DEFAULT_AFFINITY);
+            hasSplit = true;
+        }
+        VisiblePosition nextParagraph = startOfParagraph(endOfParagraph(currentParagraphStart).next());
+        if (nextParagraph.isNull())
+            break;
+        currentParagraphStart = nextParagraph;
+    }
+    return hasSplit;
+}
+
+}
diff --git a/WebCore/editing/ApplyBlockElementCommand.h b/WebCore/editing/ApplyBlockElementCommand.h
new file mode 100644 (file)
index 0000000..898b285
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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.
+ */
+
+#ifndef ApplyBlockElementCommand_h
+#define ApplyBlockElementCommand_h
+
+#include "CompositeEditCommand.h"
+
+namespace WebCore {
+
+class ApplyBlockElementCommand : public CompositeEditCommand {
+protected:
+    ApplyBlockElementCommand(Document*, const QualifiedName& tagName, const AtomicString& className, const AtomicString& inlineStyle);
+    ApplyBlockElementCommand(Document*, const QualifiedName& tagName);
+
+    virtual void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection);
+    PassRefPtr<Element> createBlockElement() const;
+    const QualifiedName tagName() const { return m_tagName; }
+
+private:
+    virtual void doApply();
+    virtual void formatParagraph(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>&) = 0;
+    bool splitTextNodes(const VisiblePosition& start, int numParagraphs);
+
+    QualifiedName m_tagName;
+    AtomicString m_className;
+    AtomicString m_inlineStyle;
+};
+
+}
+
+#endif
index dda2501..d8b504e 100644 (file)
@@ -26,6 +26,7 @@
 // This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build.
 
 #include <AppendNodeCommand.cpp>
+#include <ApplyBlockElementCommand.cpp>
 #include <ApplyStyleCommand.cpp>
 #include <BreakBlockquoteCommand.cpp>
 #include <CompositeEditCommand.cpp>
index 7443f42..a7a5af5 100644 (file)
@@ -434,7 +434,14 @@ static bool executeFormatBlock(Frame* frame, Event*, EditorCommandSource, const
         tagName = tagName.substring(1, tagName.length() - 2);
     if (!validBlockTag(tagName))
         return false;
-    applyCommand(FormatBlockCommand::create(frame->document(), tagName));
+
+    ExceptionCode ec;
+    String localName, prefix;
+    if (!Document::parseQualifiedName(tagName, prefix, localName, ec))
+        return false;
+    QualifiedName qualifiedTagName(prefix, localName, xhtmlNamespaceURI);
+
+    applyCommand(FormatBlockCommand::create(frame->document(), qualifiedTagName));
     return true;
 }
 
index 6bb8ad5..e4ec872 100644 (file)
@@ -36,66 +36,16 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
-FormatBlockCommand::FormatBlockCommand(Document* document, const AtomicString& tagName) 
-    : CompositeEditCommand(document), m_tagName(tagName)
+FormatBlockCommand::FormatBlockCommand(Document* document, const QualifiedName& tagName) 
+    : ApplyBlockElementCommand(document, tagName)
 {
 }
 
-void FormatBlockCommand::doApply()
+void FormatBlockCommand::formatParagraph(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>&)
 {
-    if (!endingSelection().isNonOrphanedCaretOrRange())
-        return;
-    
-    if (!endingSelection().rootEditableElement())
-        return;
-
-    VisiblePosition visibleEnd = endingSelection().visibleEnd();
-    VisiblePosition visibleStart = endingSelection().visibleStart();
-    // When a selection ends at the start of a paragraph, we rarely paint 
-    // the selection gap before that paragraph, because there often is no gap.  
-    // In a case like this, it's not obvious to the user that the selection 
-    // ends "inside" that paragraph, so it would be confusing if FormatBlock
-    // operated on that paragraph.
-    // FIXME: We paint the gap before some paragraphs that are indented with left 
-    // margin/padding, but not others.  We should make the gap painting more consistent and 
-    // then use a left margin/padding rule here.
-    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) {
-        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));
-        visibleEnd = endingSelection().visibleEnd();
-    }
-
-    VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd);
-    if (endingSelection().isCaret() || startOfParagraph(visibleStart) == startOfLastParagraph) {
-        doApplyForSingleParagraph();
-        return;
-    }
-
-    setEndingSelection(visibleStart);
-    doApplyForSingleParagraph();
-    visibleStart = endingSelection().visibleStart();
-    VisiblePosition nextParagraph = endOfParagraph(visibleStart).next();
-    while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) {
-        setEndingSelection(nextParagraph);
-        doApplyForSingleParagraph();
-        nextParagraph = endOfParagraph(endingSelection().visibleStart()).next();
-    }
-    setEndingSelection(visibleEnd);
-    doApplyForSingleParagraph();
-    visibleEnd = endingSelection().visibleEnd();
-
-    setEndingSelection(VisibleSelection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM));
-}
-
-void FormatBlockCommand::doApplyForSingleParagraph()
-{
-    ExceptionCode ec;
-    String localName, prefix;
-    if (!Document::parseQualifiedName(m_tagName, prefix, localName, ec))
-        return;
-    QualifiedName qTypeOfBlock(prefix, localName, xhtmlNamespaceURI);
-
+    setEndingSelection(endOfCurrentParagraph);
     Node* refNode = enclosingBlockFlowElement(endingSelection().visibleStart());
-    if (refNode->hasTagName(qTypeOfBlock))
+    if (refNode->hasTagName(tagName()))
         // We're already in a block with the format we want, so we don't have to do anything
         return;
 
@@ -103,7 +53,7 @@ void FormatBlockCommand::doApplyForSingleParagraph()
     VisiblePosition paragraphEnd = endOfParagraph(endingSelection().visibleStart());
     VisiblePosition blockStart = startOfBlock(endingSelection().visibleStart());
     VisiblePosition blockEnd = endOfBlock(endingSelection().visibleStart());
-    RefPtr<Element> blockNode = createHTMLElement(document(), m_tagName);
+    RefPtr<Element> blockNode = createBlockElement();
     RefPtr<Element> placeholder = createBreakElement(document());
 
     Node* root = endingSelection().start().node()->rootEditableElement();
index 70e6fa6..519ce37 100644 (file)
 #ifndef FormatBlockCommand_h
 #define FormatBlockCommand_h
 
+#include "ApplyBlockElementCommand.h"
 #include "CompositeEditCommand.h"
 
 namespace WebCore {
 
-class FormatBlockCommand : public CompositeEditCommand {
+class FormatBlockCommand : public ApplyBlockElementCommand {
 public:
-    static PassRefPtr<FormatBlockCommand> create(Document* document, const AtomicString& tagName)
+    static PassRefPtr<FormatBlockCommand> create(Document* document, const QualifiedName& tagName)
     {
         return adoptRef(new FormatBlockCommand(document, tagName));
     }
 
 private:
-    FormatBlockCommand(Document*, const AtomicString& tagName);
+    FormatBlockCommand(Document*, const QualifiedName& tagName);
 
-    virtual void doApply();
-    void doApplyForSingleParagraph();
+    virtual void formatParagraph(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>&);
     virtual EditAction editingAction() const { return EditActionFormatBlock; }
-
-    AtomicString m_tagName;
 };
 
 } // namespace WebCore
index d4ffe7f..1fe801a 100644 (file)
@@ -44,43 +44,15 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
-static String indentBlockquoteString()
-{
-    DEFINE_STATIC_LOCAL(String, string, ("webkit-indent-blockquote"));
-    return string;
-}
-
-static PassRefPtr<HTMLBlockquoteElement> createIndentBlockquoteElement(Document* document)
-{
-    RefPtr<HTMLBlockquoteElement> element = HTMLBlockquoteElement::create(document);
-    element->setAttribute(classAttr, indentBlockquoteString());
-    element->setAttribute(styleAttr, "margin: 0 0 0 40px; border: none; padding: 0px;");
-    return element.release();
-}
-
 static bool isListOrIndentBlockquote(const Node* node)
 {
     return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag));
 }
 
-// This function can return -1 if we are unable to count the paragraphs between |start| and |end|.
-static int countParagraphs(const VisiblePosition& endOfFirstParagraph, const VisiblePosition& endOfLastParagraph)
-{
-    int count = 0;
-    VisiblePosition cur = endOfFirstParagraph;
-    while (cur != endOfLastParagraph) {
-        ++count;
-        cur = endOfParagraph(cur.next());
-        // If start is before a table and end is inside a table, we will never hit end because the
-        // whole table is considered a single paragraph.
-        if (cur.isNull())
-            return -1;
-    }
-    return count;
-}
-
 IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels)
-    : CompositeEditCommand(document), m_typeOfAction(typeOfAction), m_marginInPixels(marginInPixels)
+    : ApplyBlockElementCommand(document, blockquoteTag, "webkit-indent-blockquote", "margin: 0 0 0 40px; border: none; padding: 0px;")
+    , m_typeOfAction(typeOfAction)
+    , m_marginInPixels(marginInPixels)
 {
 }
 
@@ -116,12 +88,10 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu
     return true;
 }
     
-void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurrentParagraph, const VisiblePosition& endOfNextParagraph, RefPtr<Element>& targetBlockquote)
+void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& targetBlockquote)
 {
-    Node* enclosingCell = 0;
-
     Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
-    enclosingCell = enclosingNodeOfType(start, &isTableCell);
+    Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
     Node* nodeToSplitTo;
     if (enclosingCell)
         nodeToSplitTo = enclosingCell;
@@ -135,97 +105,11 @@ void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurr
     if (!targetBlockquote) {
         // Create a new blockquote and insert it as a child of the root editable element. We accomplish
         // this by splitting all parents of the current paragraph up to that point.
-        targetBlockquote = createIndentBlockquoteElement(document());
+        targetBlockquote = createBlockElement();
         insertNodeBefore(targetBlockquote, outerBlock);
     }
 
     moveParagraphWithClones(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, targetBlockquote.get(), outerBlock.get());
-    
-    // Don't put the next paragraph in the blockquote we just created for this paragraph unless 
-    // the next paragraph is in the same cell.
-    if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
-        targetBlockquote = 0;
-}
-
-void IndentOutdentCommand::indentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
-{
-    // Special case empty unsplittable elements because there's nothing to split
-    // and there's nothing to move.
-    Position start = startOfSelection.deepEquivalent().downstream();
-    if (isAtUnsplittableElement(start)) {
-        RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
-        insertNodeAt(blockquote, start);
-        RefPtr<Element> placeholder = createBreakElement(document());
-        appendNode(placeholder, blockquote);
-        setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
-        return;
-    }
-
-    RefPtr<Element> blockquoteForNextIndent;
-    VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
-    VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
-    int endOfCurrentParagraphIndex = indexForVisiblePosition(endOfCurrentParagraph);
-    int endAfterSelectionIndex = indexForVisiblePosition(endAfterSelection);
-
-    // When indenting within a <pre> tag, we need to split each paragraph into a separate node for moveParagraphWithClones to work.
-    // However, splitting text nodes can cause endOfCurrentParagraph and endAfterSelection to point to an invalid position if we
-    // changed the text node it was pointing at.  So we have to reset these positions.
-    int numParagraphs = countParagraphs(endOfCurrentParagraph, endAfterSelection);
-    if (splitTextNodes(startOfParagraph(startOfSelection), numParagraphs + 1)) {
-        RefPtr<Range> endOfCurrentParagraphRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endOfCurrentParagraphIndex, 0, true);
-        RefPtr<Range> endAfterSelectionRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endAfterSelectionIndex, 0, true);
-        if (!endOfCurrentParagraphRange.get() || !endAfterSelectionRange.get()) {
-            ASSERT_NOT_REACHED();
-            return;
-        }
-        endOfCurrentParagraph = VisiblePosition(endOfCurrentParagraphRange->startPosition(), DOWNSTREAM);
-        endAfterSelection = VisiblePosition(endAfterSelectionRange->startPosition(), DOWNSTREAM);
-    }
-
-    while (endOfCurrentParagraph != endAfterSelection) {
-        // Iterate across the selected paragraphs...
-        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
-        if (tryIndentingAsListItem(endOfCurrentParagraph))
-            blockquoteForNextIndent = 0;
-        else
-            indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent);
-
-        // indentIntoBlockquote could move more than one paragraph if the paragraph
-        // is in a list item or a table. As a result, endAfterSelection could refer to a position
-        // no longer in the document.
-        if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().node()->inDocument())
-            break;
-        // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node()
-        // If somehow we did, return to prevent crashes.
-        if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) {
-            ASSERT_NOT_REACHED();
-            return;
-        }
-        endOfCurrentParagraph = endOfNextParagraph;
-    }   
-}
-
-// Returns true if at least one text node was split.
-bool IndentOutdentCommand::splitTextNodes(const VisiblePosition& start, int numParagraphs)
-{
-    VisiblePosition currentParagraphStart = start;
-    bool hasSplit = false;
-    int paragraphCount;
-    for (paragraphCount = 0; paragraphCount < numParagraphs; ++paragraphCount) {
-        // If there are multiple paragraphs in a single text node, we split the text node into a separate node for each paragraph.
-        if (currentParagraphStart.deepEquivalent().node()->isTextNode() && currentParagraphStart.deepEquivalent().node() == startOfParagraph(currentParagraphStart.previous()).deepEquivalent().node()) {
-            Text* textNode = static_cast<Text *>(currentParagraphStart.deepEquivalent().node());
-            int offset = currentParagraphStart.deepEquivalent().offsetInContainerNode();
-            splitTextNode(textNode, offset);
-            currentParagraphStart = VisiblePosition(textNode, 0, VP_DEFAULT_AFFINITY);
-            hasSplit = true;
-        }
-        VisiblePosition nextParagraph = startOfParagraph(endOfParagraph(currentParagraphStart).next());
-        if (nextParagraph.isNull())
-            break;
-        currentParagraphStart = nextParagraph;
-    }
-    return hasSplit;
 }
 
 void IndentOutdentCommand::outdentParagraph()
@@ -291,6 +175,7 @@ void IndentOutdentCommand::outdentParagraph()
     moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true);
 }
 
+// FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection
 void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
 {
     VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection);
@@ -327,48 +212,20 @@ void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection
     }
 }
 
-void IndentOutdentCommand::doApply()
+void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
 {
-    if (!endingSelection().isNonOrphanedCaretOrRange())
-        return;
-
-    if (!endingSelection().rootEditableElement())
-        return;
-        
-    VisiblePosition visibleEnd = endingSelection().visibleEnd();
-    VisiblePosition visibleStart = endingSelection().visibleStart();
-    // When a selection ends at the start of a paragraph, we rarely paint 
-    // the selection gap before that paragraph, because there often is no gap.  
-    // In a case like this, it's not obvious to the user that the selection 
-    // ends "inside" that paragraph, so it would be confusing if Indent/Outdent 
-    // operated on that paragraph.
-    // FIXME: We paint the gap before some paragraphs that are indented with left 
-    // margin/padding, but not others.  We should make the gap painting more consistent and 
-    // then use a left margin/padding rule here.
-    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
-        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));
-
-    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
-    VisiblePosition startOfSelection = selection.visibleStart();
-    VisiblePosition endOfSelection = selection.visibleEnd();
-    
-    int startIndex = indexForVisiblePosition(startOfSelection);
-    int endIndex = indexForVisiblePosition(endOfSelection);
-    
-    ASSERT(!startOfSelection.isNull());
-    ASSERT(!endOfSelection.isNull());
-    
     if (m_typeOfAction == Indent)
-        indentRegion(startOfSelection, endOfSelection);
+        ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelection);
     else
         outdentRegion(startOfSelection, endOfSelection);
+}
 
-    updateLayout();
-    
-    RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
-    RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
-    if (startRange && endRange)
-        setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
+void IndentOutdentCommand::formatParagraph(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& blockquoteForNextIndent)
+{
+    if (tryIndentingAsListItem(endOfCurrentParagraph))
+        blockquoteForNextIndent = 0;
+    else
+        indentIntoBlockquote(endOfCurrentParagraph, blockquoteForNextIndent);
 }
 
 }
index 8644cc5..6777258 100644 (file)
 #ifndef IndentOutdentCommand_h
 #define IndentOutdentCommand_h
 
+#include "ApplyBlockElementCommand.h"
 #include "CompositeEditCommand.h"
 
 namespace WebCore {
 
-class IndentOutdentCommand : public CompositeEditCommand {
+class IndentOutdentCommand : public ApplyBlockElementCommand {
 public:
     enum EIndentType { Indent, Outdent };
     static PassRefPtr<IndentOutdentCommand> create(Document* document, EIndentType type, int marginInPixels = 0)
     {
         return adoptRef(new IndentOutdentCommand(document, type, marginInPixels));
     }
-    
+
     virtual bool preservesTypingStyle() const { return true; }
 
 private:
     IndentOutdentCommand(Document*, EIndentType, int marginInPixels);
 
-    virtual void doApply();
     virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; }
 
     void indentRegion(const VisiblePosition&, const VisiblePosition&);
     void outdentRegion(const VisiblePosition&, const VisiblePosition&);
     void outdentParagraph();
     bool tryIndentingAsListItem(const VisiblePosition&);
-    void indentIntoBlockquote(const VisiblePosition&, const VisiblePosition&, RefPtr<Element>&);
-    bool splitTextNodes(const VisiblePosition& start, int numParagraphs);
+    void indentIntoBlockquote(const VisiblePosition&, RefPtr<Element>&);
+
+    void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection);
+    void formatParagraph(const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& blockquoteForNextIndent);
 
     EIndentType m_typeOfAction;
     int m_marginInPixels;