LayoutTests:
authorlweintraub <lweintraub@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Jun 2006 18:17:58 +0000 (18:17 +0000)
committerlweintraub <lweintraub@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Jun 2006 18:17:58 +0000 (18:17 +0000)
        Reviewed by justin

        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
        Bug 7568: Implement Indent/Outdent

        * editing/execCommand/indent-list-item-expected.checksum: Added.
        * editing/execCommand/indent-list-item-expected.png: Added.
        * editing/execCommand/indent-list-item-expected.txt: Added.
        * editing/execCommand/indent-list-item.html: Added.
        * editing/execCommand/indent-selection-expected.checksum: Added.
        * editing/execCommand/indent-selection-expected.png: Added.
        * editing/execCommand/indent-selection-expected.txt: Added.
        * editing/execCommand/indent-selection.html: Added.
        * editing/execCommand/outdent-selection.html: Added.

WebCore:

        Reviewed by justin

        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
        Bug 7568: Implement Indent/Outdent

        * WebCore.vcproj/WebCore/WebCore.vcproj: Added IndentOutdentCommand.h/cpp
        * WebCore.xcodeproj/project.pbxproj: Ditto.
        * editing/IndentOutdentCommand.cpp: Added.
        (WebCore::IndentOutdentCommand::IndentOutdentCommand):
        (WebCore::enclosingListOrBlockquote):
        (WebCore::IndentOutdentCommand::splitTreeToNode): Splits the DOM tree from a
        descendent node to an ending ancestor, duplicating nodes when necessary. Returns
        the last node split. Used to insert blockquotes at the topmost level.
        (WebCore::IndentOutdentCommand::indentRegion):
        (WebCore::IndentOutdentCommand::outdentParagraph):
        (WebCore::IndentOutdentCommand::outdentRegion):
        (WebCore::IndentOutdentCommand::doApply):
        * editing/IndentOutdentCommand.h: Added.
        (WebCore::IndentOutdentCommand::):
        * editing/JSEditor.cpp: Added the execCommands Indent and Outdent
        * editing/htmlediting.cpp:
        (WebCore::enclosingNodeWithTag): Finds the enclosing node with any specified tag.
        (WebCore::enclosingListChild): Added checks for nill and editable boundaries.
        (WebCore::outermostEnclosingListChild): Returns the highest ancestor list child.
        (WebCore::highestAncestor): Added.
        (WebCore::createElement): Changed the passed in string to a const reference.
        * editing/htmlediting.h:

WebKit:

        Reviewed by justin

        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
        Bug 7568: Implement Indent/Outdent
        Added undo action strings and enum values

        * English.lproj/Localizable.strings:
        * WebCoreSupport/WebFrameBridge.m:
        (-[WebFrameBridge nameForUndoAction:]):

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

13 files changed:
LayoutTests/ChangeLog
WebCore/ChangeLog
WebCore/WebCore.vcproj/WebCore/WebCore.vcproj
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/editing/EditAction.h
WebCore/editing/IndentOutdentCommand.cpp [new file with mode: 0755]
WebCore/editing/IndentOutdentCommand.h [new file with mode: 0755]
WebCore/editing/JSEditor.cpp
WebCore/editing/htmlediting.cpp
WebCore/editing/htmlediting.h
WebKit/ChangeLog
WebKit/English.lproj/Localizable.strings
WebKit/WebCoreSupport/WebFrameBridge.m

index 2ce10080cbfee68f481ff9261282d05ae72d7a7a..b9bc95c5faa8a6b797e75bac852708b524cd105b 100644 (file)
@@ -1,3 +1,20 @@
+2006-06-28  Levi Weintraub  <lweintraub@apple.com>
+
+        Reviewed by justin
+
+        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
+        Bug 7568: Implement Indent/Outdent
+
+        * editing/execCommand/indent-list-item-expected.checksum: Added.
+        * editing/execCommand/indent-list-item-expected.png: Added.
+        * editing/execCommand/indent-list-item-expected.txt: Added.
+        * editing/execCommand/indent-list-item.html: Added.
+        * editing/execCommand/indent-selection-expected.checksum: Added.
+        * editing/execCommand/indent-selection-expected.png: Added.
+        * editing/execCommand/indent-selection-expected.txt: Added.
+        * editing/execCommand/indent-selection.html: Added.
+        * editing/execCommand/outdent-selection.html: Added.
+
 2006-06-28  Mitz Pettel  <opendarwin.org@mitzpettel.com>
 
         Reviewed by Darin and Hyatt.
index e47eb684091ec810da03cc4f3e2a897c9b819b3e..33403349dc35fe8eabd83c17c7b12741542d3daf 100644 (file)
@@ -1,3 +1,33 @@
+2006-06-28  Levi Weintraub  <lweintraub@apple.com>
+
+        Reviewed by justin
+
+        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
+        Bug 7568: Implement Indent/Outdent
+
+        * WebCore.vcproj/WebCore/WebCore.vcproj: Added IndentOutdentCommand.h/cpp
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * editing/IndentOutdentCommand.cpp: Added.
+        (WebCore::IndentOutdentCommand::IndentOutdentCommand):
+        (WebCore::enclosingListOrBlockquote):
+        (WebCore::IndentOutdentCommand::splitTreeToNode): Splits the DOM tree from a
+        descendent node to an ending ancestor, duplicating nodes when necessary. Returns
+        the last node split. Used to insert blockquotes at the topmost level.
+        (WebCore::IndentOutdentCommand::indentRegion):
+        (WebCore::IndentOutdentCommand::outdentParagraph):
+        (WebCore::IndentOutdentCommand::outdentRegion):
+        (WebCore::IndentOutdentCommand::doApply):
+        * editing/IndentOutdentCommand.h: Added.
+        (WebCore::IndentOutdentCommand::):
+        * editing/JSEditor.cpp: Added the execCommands Indent and Outdent
+        * editing/htmlediting.cpp:
+        (WebCore::enclosingNodeWithTag): Finds the enclosing node with any specified tag.
+        (WebCore::enclosingListChild): Added checks for nill and editable boundaries.
+        (WebCore::outermostEnclosingListChild): Returns the highest ancestor list child.
+        (WebCore::highestAncestor): Added.
+        (WebCore::createElement): Changed the passed in string to a const reference.
+        * editing/htmlediting.h:
+
 2006-06-28  Mitz Pettel  <opendarwin.org@mitzpettel.com>
 
         Reviewed by Darin and Hyatt.
         (WebCore::SQLStatement::getColumnBlobAsVector):
         (WebCore::SQLStatement::getColumnBlob):
 
+>>>>>>> .r15079
 2006-06-27  Ada Chan <adachan@apple.com>
 
         Reviewed by sfalken.
index 4b22c9164e1f0390b30f4bf99775b6da8e3044f6..b94fad900b8521a893732409364632c0b71b68c6 100644 (file)
                                RelativePath="..\..\editing\InsertListCommand.h"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath="..\..\editing\IndentOutdentCommand.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\..\editing\IndentOutdentCommand.h"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath="..\..\editing\InsertNodeBeforeCommand.cpp"\r
                                >\r
index 79fc24f20f35966b2147b50a2d41dd6e021702b3..48362d0e4155d0fac06577ee86115442c5180e9b 100644 (file)
                D086FE9909D53AAB005BC74D /* UnlinkCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D086FE9709D53AAB005BC74D /* UnlinkCommand.cpp */; };
                D0B0556809C6700100307E43 /* CreateLinkCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = D0B0556609C6700100307E43 /* CreateLinkCommand.h */; };
                D0B0556909C6700100307E43 /* CreateLinkCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0B0556709C6700100307E43 /* CreateLinkCommand.cpp */; };
+               DB23C2CB0A508D29002489EB /* IndentOutdentCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DB23C2C90A508D29002489EB /* IndentOutdentCommand.cpp */; };
+               DB23C2CC0A508D29002489EB /* IndentOutdentCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = DB23C2CA0A508D29002489EB /* IndentOutdentCommand.h */; };
                DD763BB20992C2C900740B8E /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DD763BB10992C2C900740B8E /* libxml2.dylib */; };
                DD7CDF250A23CF9800069928 /* CSSUnknownRule.h in Headers */ = {isa = PBXBuildFile; fileRef = A80E6CCE0A1989CA007FB8C5 /* CSSUnknownRule.h */; };
                E1052C320A4D70010072D99B /* DOMEventsNonstandard.mm in Sources */ = {isa = PBXBuildFile; fileRef = E1052C310A4D70010072D99B /* DOMEventsNonstandard.mm */; };
                D086FE9709D53AAB005BC74D /* UnlinkCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnlinkCommand.cpp; sourceTree = "<group>"; };
                D0B0556609C6700100307E43 /* CreateLinkCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CreateLinkCommand.h; sourceTree = "<group>"; };
                D0B0556709C6700100307E43 /* CreateLinkCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CreateLinkCommand.cpp; sourceTree = "<group>"; };
+               DB23C2C90A508D29002489EB /* IndentOutdentCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IndentOutdentCommand.cpp; sourceTree = "<group>"; };
+               DB23C2CA0A508D29002489EB /* IndentOutdentCommand.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IndentOutdentCommand.h; sourceTree = "<group>"; };
                DD763BB10992C2C900740B8E /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = /usr/lib/libxml2.dylib; sourceTree = "<absolute>"; };
                E1052C310A4D70010072D99B /* DOMEventsNonstandard.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = DOMEventsNonstandard.mm; sourceTree = "<group>"; };
                E1EE773508F1086C00166870 /* WebCoreTextDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = WebCoreTextDecoder.h; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
                93309D86099E64910056E581 /* editing */ = {
                        isa = PBXGroup;
                        children = (
+                               DB23C2C90A508D29002489EB /* IndentOutdentCommand.cpp */,
+                               DB23C2CA0A508D29002489EB /* IndentOutdentCommand.h */,
                                D05CED270A40BB2C00C5AF38 /* FormatBlockCommand.cpp */,
                                D05CED280A40BB2C00C5AF38 /* FormatBlockCommand.h */,
                                93309D87099E64910056E581 /* AppendNodeCommand.cpp */,
                                85031B4F0A44EFC700F992E0 /* UIEventWithKeyState.h in Headers */,
                                85031B510A44EFC700F992E0 /* WheelEvent.h in Headers */,
                                ABE7B5240A489F830031881C /* DeprecatedRenderSelect.h in Headers */,
+                               DB23C2CC0A508D29002489EB /* IndentOutdentCommand.h in Headers */,
                                BCCD74DC0A4C8D35005FDA6D /* HTMLViewSourceDocument.h in Headers */,
                                1A8086CC0A4D097600DFB6A7 /* DOMCSSInternal.h in Headers */,
                        );
                                85031B500A44EFC700F992E0 /* WheelEvent.cpp in Sources */,
                                ABE7B5230A489F830031881C /* DeprecatedRenderSelect.cpp in Sources */,
                                51F11E150A48C2920034A24E /* SQLTransaction.cpp in Sources */,
+                               DB23C2CB0A508D29002489EB /* IndentOutdentCommand.cpp in Sources */,
                                BCCD74E50A4C8DDF005FDA6D /* HTMLViewSourceDocument.cpp in Sources */,
                                E1052C320A4D70010072D99B /* DOMEventsNonstandard.mm in Sources */,
                                516149ED0A525E3A003DFC7A /* SiteIcon.cpp in Sources */,
index 88a9f051d896fdf7cf7cec71fec3f59e2a1580e7..177bf7de3e68b69414e1e6ea2d714c081c10be0f 100644 (file)
@@ -62,7 +62,9 @@ namespace WebCore {
         EditActionCreateLink,
         EditActionUnlink,
         EditActionFormatBlock,
-        EditActionInsertList
+        EditActionInsertList,
+        EditActionIndent,
+        EditActionOutdent
     } EditAction;    
 }
 
diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp
new file mode 100755 (executable)
index 0000000..0a0462d
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2006 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 (IndentOutdentCommandINCLUDING, 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 "Element.h"
+#include "IndentOutdentCommand.h"
+#include "InsertListCommand.h"
+#include "Document.h"
+#include "htmlediting.h"
+#include "HTMLElement.h"
+#include "HTMLNames.h"
+#include "InsertLineBreakCommand.h"
+#include "Range.h"
+#include "SplitElementCommand.h"
+#include "visible_units.h"
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels)
+    : CompositeEditCommand(document), m_typeOfAction(typeOfAction), m_marginInPixels(marginInPixels)
+{}
+
+static Node* enclosingListOrBlockquote(Node* node)
+{
+    if (!node)
+        return 0;
+    Node* root = (node->inDocument()) ? node->rootEditableElement() : highestAncestor(node);
+    ASSERT(root);
+    for (Node* n = node->parentNode(); n && (n == root || n->isAncestor(root)); n = n->parentNode())
+        if (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(blockquoteTag))
+            return n;
+            
+    return 0;
+}
+
+// This function is a workaround for moveParagraph's tendency to strip blockquotes. It updates lastBlockquote to point to the
+// correct level for the current paragraph, and returns a pointer to a placeholder br where the insertion should be performed.
+Node* IndentOutdentCommand::prepareBlockquoteLevelForInsertion(VisiblePosition& currentParagraph, Node** lastBlockquote)
+{
+    int currentBlockquoteLevel = 0;
+    int lastBlockquoteLevel = 0;
+    Node* node = currentParagraph.deepEquivalent().node();
+    while ((node = enclosingNodeWithTag(node, blockquoteTag)))
+        currentBlockquoteLevel++;
+    node = *lastBlockquote;
+    while ((node = enclosingNodeWithTag(node, blockquoteTag)))
+        lastBlockquoteLevel++;
+    while (currentBlockquoteLevel > lastBlockquoteLevel) {
+        RefPtr<Node> newBlockquote = createElement(document(), "blockquote");
+        appendNode(newBlockquote.get(), *lastBlockquote);
+        *lastBlockquote = newBlockquote.get();
+        lastBlockquoteLevel++;
+    }
+    while (currentBlockquoteLevel < lastBlockquoteLevel) {
+        *lastBlockquote = enclosingNodeWithTag(*lastBlockquote, blockquoteTag);
+        lastBlockquoteLevel--;
+    }
+    RefPtr<Node> placeholder = createBreakElement(document());
+    if ((*lastBlockquote)->firstChild() && !(*lastBlockquote)->lastChild()->hasTagName(brTag)) {
+        RefPtr<Node> collapsedPlaceholder = createBreakElement(document());
+        appendNode(collapsedPlaceholder.get(), (*lastBlockquote));
+    }
+    appendNode(placeholder.get(), *lastBlockquote);
+    return placeholder.get();
+}
+
+// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
+// to determine if the split is necessary. Returns the last split node.
+Node* IndentOutdentCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor)
+{
+    Node* node;
+    for (node = start; node && node->parent() != end; node = node->parent()) {
+        VisiblePosition positionInParent(Position(node->parent(), 0), DOWNSTREAM);
+        VisiblePosition positionInNode(Position(node, 0), DOWNSTREAM);
+        if (positionInParent != positionInNode) {
+            EditCommandPtr cmd(new SplitElementCommand(document(), static_cast<Element*>(node->parent()), node));
+            applyCommandToComposite(cmd);
+        }
+    }
+    if (splitAncestor)
+        return splitTreeToNode(end, end->parent());
+    return node;
+}
+
+void IndentOutdentCommand::indentRegion()
+{
+    VisiblePosition startOfSelection = endingSelection().visibleStart();
+    VisiblePosition endOfSelection = endingSelection().visibleEnd();
+
+    ASSERT(!startOfSelection.isNull());
+    ASSERT(!endOfSelection.isNull());
+    
+    Node* previousListNode = 0;
+    Node* newListNode;
+    Node* newBlockquote = 0;
+    VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
+    VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
+    while (endOfCurrentParagraph != endAfterSelection) {
+        // Iterate across the selected paragraphs...
+        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+        Node* listNode = enclosingList(endOfCurrentParagraph.deepEquivalent().node());
+        Node* insertionPoint;
+        if (listNode) {
+            RefPtr<Node> placeholder = createBreakElement(document());
+            insertionPoint = placeholder.get();
+            newBlockquote = 0;
+            RefPtr<Node> listItem = createListItemElement(document());
+            if (listNode == previousListNode) {
+                // The previous paragraph was inside the same list, so add this list item to the list we already created
+                appendNode(listItem.get(), newListNode);
+                appendNode(placeholder.get(), listItem.get());
+            } else {
+                // Clone the list element, insert it before the current paragraph, and move the paragraph into it.
+                RefPtr<Node> clonedList = static_cast<Element*>(listNode)->cloneNode(false);
+                insertNodeBefore(clonedList.get(), enclosingListChild(endOfCurrentParagraph.deepEquivalent().node()));
+                appendNode(listItem.get(), clonedList.get());
+                appendNode(placeholder.get(), listItem.get());
+                newListNode = clonedList.get();
+                previousListNode = listNode;
+            }
+        } else if (newBlockquote)
+            // The previous paragraph was put into a new blockquote, so move this paragraph there as well
+            insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, &newBlockquote);
+        else {
+            // 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.
+            RefPtr<Node> blockquote = createElement(document(), "blockquote");
+            Node* startNode = startOfParagraph(endOfCurrentParagraph).deepEquivalent().node();
+            Node* startOfNewBlock = splitTreeToNode(startNode, startNode->rootEditableElement());
+            insertNodeBefore(blockquote.get(), startOfNewBlock);
+            newBlockquote = blockquote.get();
+            insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, &newBlockquote);
+        }
+        moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true);
+        endOfCurrentParagraph = endOfNextParagraph;
+    }
+}
+
+void IndentOutdentCommand::outdentParagraph()
+{
+    VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart());
+    VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);
+
+    Node* enclosingNode = enclosingListOrBlockquote(visibleStartOfParagraph.deepEquivalent().node());
+    if (!enclosingNode)
+        return;
+
+    // Handle the list case
+    bool inList = false;
+    InsertListCommand::EListType typeOfList;
+    if (enclosingNode->hasTagName(olTag)) {
+        inList = true;
+        typeOfList = InsertListCommand::OrderedListType;
+    } else if (enclosingNode->hasTagName(ulTag)) {
+        inList = true;
+        typeOfList = InsertListCommand::UnorderedListType;
+    }
+    if (inList) {
+        // Use InsertListCommand to remove the selection from the list
+        EditCommandPtr cmd(new InsertListCommand(document(), typeOfList, ""));
+        applyCommandToComposite(cmd);
+        return;
+    }
+    // The selection is inside a blockquote
+    VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0));
+    VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock);
+    VisiblePosition endOfEnclosingBlock = endOfBlock(positionInEnclosingBlock);
+    if (visibleStartOfParagraph == startOfEnclosingBlock &&
+        visibleEndOfParagraph == endOfEnclosingBlock) {
+        // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed.
+        removeNodePreservingChildren(enclosingNode);
+        return;
+    }
+    Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph);
+    Node* splitBlockquoteNode = enclosingNode;
+    if (enclosingBlockFlow != enclosingNode)
+        splitBlockquoteNode = splitTreeToNode(enclosingBlockFlowElement(visibleStartOfParagraph), enclosingNode, true);
+    RefPtr<Node> placeholder = createBreakElement(document());
+    insertNodeBefore(placeholder.get(), splitBlockquoteNode);
+    moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true);
+}
+
+void IndentOutdentCommand::outdentRegion()
+{
+    VisiblePosition startOfSelection = endingSelection().visibleStart();
+    VisiblePosition endOfSelection = endingSelection().visibleEnd();
+    VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection);
+
+    ASSERT(!startOfSelection.isNull());
+    ASSERT(!endOfSelection.isNull());
+
+    if (endOfParagraph(startOfSelection) == endOfLastParagraph) {
+        outdentParagraph();
+        return;
+    }
+
+    Position originalSelectionEnd = endingSelection().end();
+    setEndingSelection(endingSelection().visibleStart());
+    outdentParagraph();
+    Position originalSelectionStart = endingSelection().start();
+    VisiblePosition endOfCurrentParagraph = endOfParagraph(endOfParagraph(endingSelection().visibleStart()).next(true));
+    VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
+    while (endOfCurrentParagraph != endAfterSelection) {
+        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+        if (endOfCurrentParagraph == endOfLastParagraph)
+            setEndingSelection(originalSelectionEnd, DOWNSTREAM);
+        else
+            setEndingSelection(endOfCurrentParagraph);
+        outdentParagraph();
+        endOfCurrentParagraph = endOfNextParagraph;
+    }
+    setEndingSelection(Selection(originalSelectionStart, endingSelection().end(), DOWNSTREAM));
+}
+
+void IndentOutdentCommand::doApply()
+{
+    if (endingSelection().isNone())
+        return;
+
+    if (!endingSelection().rootEditableElement())
+        return;
+
+    if (m_typeOfAction == Indent)
+        indentRegion();
+    else
+        outdentRegion();
+}
+
+}
diff --git a/WebCore/editing/IndentOutdentCommand.h b/WebCore/editing/IndentOutdentCommand.h
new file mode 100755 (executable)
index 0000000..78cd456
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006 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. 
+ */
+
+#ifndef IndentOutdentCommand_h
+#define IndentOutdentCommand_h
+
+#include "CompositeEditCommand.h"
+
+namespace WebCore {
+
+class IndentOutdentCommand : public CompositeEditCommand
+{
+public:
+    enum EIndentType { Indent, Outdent };
+    IndentOutdentCommand(WebCore::Document*, EIndentType, int marginInPixels = 0);
+    virtual void doApply();
+    virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; }
+private:
+    void splitTreeTo(Node* start, Node* stop);
+    bool modifyRange();
+    EIndentType m_typeOfAction;
+    int m_marginInPixels;
+    void indentRegion();
+    void outdentRegion();
+    void outdentParagraph();
+    Node* splitTreeToNode(Node*, Node*, bool splitAncestor = false);
+    Node* prepareBlockquoteLevelForInsertion(VisiblePosition&, Node**);
+};
+
+} // namespace WebCore
+
+#endif // IndentOutdentCommand_h
index 81ca8e1739fa3d1f1ae3a5c482127a61fadd85e0..f3f4403198905f5ae0048881b6505cebcf19804a 100644 (file)
@@ -34,6 +34,7 @@
 #include "Frame.h"
 #include "HTMLNames.h"
 #include "HTMLImageElement.h"
+#include "IndentOutdentCommand.h"
 #include "InsertListCommand.h"
 #include "ReplaceSelectionCommand.h"
 #include "SelectionController.h"
@@ -314,8 +315,8 @@ bool execInsertImage(Frame* frame, bool userInterface, const String& value)
 
 bool execIndent(Frame *frame, bool userInterface, const String &value)
 {
-    // FIXME: Implement.
-    return false;
+    EditCommandPtr(new IndentOutdentCommand(frame->document(), IndentOutdentCommand::Indent)).apply();
+    return true;
 }
 
 bool execInsertLineBreak(Frame *frame, bool userInterface, const String &value)
@@ -382,8 +383,8 @@ bool execJustifyRight(Frame *frame, bool userInterface, const String &value)
 
 bool execOutdent(Frame *frame, bool userInterface, const String &value)
 {
-    // FIXME: Implement.
-    return false;
+    EditCommandPtr(new IndentOutdentCommand(frame->document(), IndentOutdentCommand::Outdent)).apply();
+    return true;
 }
 
 bool execPaste(Frame *frame, bool userInterface, const String &value)
index 52b7cc5a5c48283b1455edd442584cdbf19fa154..62014e6cd42d36431c077b8f674295f03c97dd95 100644 (file)
@@ -42,15 +42,6 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
-static Node* highestAncestor(Node* node)
-{
-    ASSERT(node);
-    Node* parent = node;
-    while ((node = node->parentNode()))
-        parent = node;
-    return parent;
-}
-
 // Atomic means that the node has no children, or has children which are ignored for the
 // purposes of editing.
 bool isAtomicNode(const Node *node)
@@ -370,6 +361,19 @@ bool isListElement(Node *n)
     return (n && (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag)));
 }
 
+Node* enclosingNodeWithTag(Node* node, const QualifiedName& tagName)
+{
+    if (!node)
+        return 0;
+    Node* root = (node->inDocument()) ? node->rootEditableElement() : highestAncestor(node);
+    ASSERT(root);
+    for (Node* n = node->parentNode(); n && (n == root || n->isAncestor(root)); n = n->parentNode())
+        if (n->hasTagName(tagName))
+            return n;
+            
+    return 0;
+}
+
 Node* enclosingTableCell(Node* node)
 {
     if (!node)
@@ -395,11 +399,15 @@ Node* enclosingList(Node* node)
     return 0;
 }
 
-Node *enclosingListChild (Node *node)
+NodeenclosingListChild (Node *node)
 {
+    if (!node)
+        return 0;
     // Check for a list item element, or for a node whose parent is a list element.  Such a node
     // will appear visually as a list item (but without a list marker)
-    for (Node *n = node; n && n->parentNode(); n = n->parentNode()) {
+    Node* root = (node->inDocument()) ? node->rootEditableElement() : highestAncestor(node);
+    ASSERT(root);
+    for (Node *n = node; n && n->parentNode() && (n == root || n->isAncestor(root)); n = n->parentNode()) {
         if (n->hasTagName(liTag) || isListElement(n->parentNode()))
             return n;
     }
@@ -407,6 +415,15 @@ Node *enclosingListChild (Node *node)
     return 0;
 }
 
+Node* outermostEnclosingListChild(Node* node)
+{
+    Node* listNode = 0;
+    Node* nextNode = node;
+    while ((nextNode = enclosingListChild(nextNode)))
+        listNode = nextNode;
+    return listNode;
+}
+
 Node* outermostEnclosingList(Node* node)
 {
     Node* listNode = 0;
@@ -416,6 +433,15 @@ Node* outermostEnclosingList(Node* node)
     return listNode;
 }
 
+Node* highestAncestor(Node* node)
+{
+    ASSERT(node);
+    Node* parent = node;
+    while ((node = node->parentNode()))
+        parent = node;
+    return parent;
+}
+
 // FIXME: do not require renderer, so that this can be used within fragments, or rename to isRenderedTable()
 bool isTableElement(Node *n)
 {
@@ -528,7 +554,7 @@ PassRefPtr<Element> createListItemElement(Document *document)
     return breakNode.release();
 }
 
-PassRefPtr<Element> createElement(Document* document, String& tagName)
+PassRefPtr<Element> createElement(Document* document, const String& tagName)
 {
     ExceptionCode ec = 0;
     RefPtr<Element> breakNode = document->createElementNS(xhtmlNamespaceURI, tagName, ec);
index 44d3dc5ee8828cf67ea806b75c5646fe95674683..339d171636cd606080a0c9c121c1f335c4a5b147 100644 (file)
@@ -27,6 +27,7 @@
 #define htmlediting_h
 
 #include <wtf/Forward.h>
+#include "HTMLNames.h"
 
 namespace WebCore {
 
@@ -62,7 +63,7 @@ PassRefPtr<Element> createBreakElement(Document*);
 PassRefPtr<Element> createOrderedListElement(Document*);
 PassRefPtr<Element> createUnorderedListElement(Document*);
 PassRefPtr<Element> createListItemElement(Document*);
-PassRefPtr<Element> createElement(Document*, String&);
+PassRefPtr<Element> createElement(Document*, const String&);
 
 bool isTabSpanNode(const Node*);
 bool isTabSpanTextNode(const Node*);
@@ -87,11 +88,13 @@ bool isLastVisiblePositionInSpecialElement(const Position&);
 Position positionAfterContainingSpecialElement(const Position&, Node** containingSpecialElement=0);
 Position positionOutsideContainingSpecialElement(const Position&, Node** containingSpecialElement=0);
 
+Node* enclosingNodeWithTag(Node*, const QualifiedName&);
 Node* enclosingTableCell(Node*);
 bool isListElement(Node*);
 Node* enclosingList(Node*);
 Node* outermostEnclosingList(Node*);
 Node* enclosingListChild(Node*);
+Node* highestAncestor(Node*);
 bool isTableElement(Node*);
 bool isFirstVisiblePositionAfterTableElement(const Position&);
 Position positionBeforePrecedingTableElement(const Position&);
index 13582bf5f617dc9211567385e533a1b2dcc94274..6e6334876dd51c9f170be24cf6e8b698fc412690 100644 (file)
@@ -1,3 +1,15 @@
+2006-06-28  Levi Weintraub  <lweintraub@apple.com>
+
+        Reviewed by justin
+
+        http://bugzilla.opendarwin.org/show_bug.cgi?id=7568
+        Bug 7568: Implement Indent/Outdent
+        Added undo action strings and enum values
+
+        * English.lproj/Localizable.strings:
+        * WebCoreSupport/WebFrameBridge.m: 
+        (-[WebFrameBridge nameForUndoAction:]):
+
 2006-06-27  Brady Eidson  <beidson@apple.com>
 
         Reviewed by Maciej
         Renamed from -initWithPath:.  Instead of releasing/deallocating self on error, return NO.
         (-[WebNetscapePluginPackage initWithPath:]):
         Call the new -_initWithPath:.  If it returns NO, unload the plugin package before deallocating it.
-        
->>>>>>> .r14837
+
 2006-06-11  Darin Adler  <darin@apple.com>
 
         - try to fix Windows build
index e5247a6093f1d7c71c54208cfa8d9b81a95512bf..254d133559e99b91b60809f91dad4681ebae6472 100644 (file)
Binary files a/WebKit/English.lproj/Localizable.strings and b/WebKit/English.lproj/Localizable.strings differ
index db47376f3bd952f486b9ba7aa07e1396aa2bb3c3..0979626cbd2d9f318093963897f9699638375fa9 100644 (file)
@@ -1583,6 +1583,8 @@ static id <WebFormDelegate> formDelegate(WebFrameBridge *self)
         case WebUndoActionUnlink: return UI_STRING_KEY("Unlink", "Unlink (Undo action name)", "Undo action name");
         case WebUndoActionInsertList: return UI_STRING_KEY("Insert List", "Insert List (Undo action name)", "Undo action name");
         case WebUndoActionFormatBlock: return UI_STRING_KEY("Formatting", "Format Block (Undo action name)", "Undo action name");
+        case WebUndoActionIndent: return UI_STRING_KEY("Indent", "Indent (Undo action name)", "Undo action name");
+        case WebUndoActionOutdent: return UI_STRING_KEY("Outdent", "Outdent (Undo action name)", "Undo action name");
     }
     return nil;
 }