1 /* 2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "Element.h" 28 #include "FormatBlockCommand.h" 29 #include "Document.h" 30 #include "htmlediting.h" 31 #include "HTMLElement.h" 32 #include "HTMLNames.h" 33 #include "visible_units.h" 34 35 namespace WebCore { 36 37 using namespace HTMLNames; 38 39 FormatBlockCommand::FormatBlockCommand(Document* document, const AtomicString& tagName) 40 : CompositeEditCommand(document), m_tagName(tagName) 41 { 42 } 43 44 bool FormatBlockCommand::modifyRange() 45 { 46 ASSERT(endingSelection().isRange()); 47 VisiblePosition visibleStart = endingSelection().visibleStart(); 48 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 49 VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd); 50 51 if (startOfParagraph(visibleStart) == startOfLastParagraph) 52 return false; 53 54 setEndingSelection(visibleStart); 55 doApply(); 56 visibleStart = endingSelection().visibleStart(); 57 VisiblePosition nextParagraph = endOfParagraph(visibleStart).next(); 58 while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) { 59 setEndingSelection(nextParagraph); 60 doApply(); 61 nextParagraph = endOfParagraph(endingSelection().visibleStart()).next(); 62 } 63 setEndingSelection(visibleEnd); 64 doApply(); 65 visibleEnd = endingSelection().visibleEnd(); 66 setEndingSelection(VisibleSelection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); 67 68 return true; 69 } 70 71 void FormatBlockCommand::doApply() 72 { 73 if (endingSelection().isNone()) 74 return; 75 76 if (!endingSelection().rootEditableElement()) 77 return; 78 79 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 80 VisiblePosition visibleStart = endingSelection().visibleStart(); 81 // When a selection ends at the start of a paragraph, we rarely paint 82 // the selection gap before that paragraph, because there often is no gap. 83 // In a case like this, it's not obvious to the user that the selection 84 // ends "inside" that paragraph, so it would be confusing if FormatBlock 85 // operated on that paragraph. 86 // FIXME: We paint the gap before some paragraphs that are indented with left 87 // margin/padding, but not others. We should make the gap painting more consistent and 88 // then use a left margin/padding rule here. 89 if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) 90 setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); 91 92 if (endingSelection().isRange() && modifyRange()) 93 return; 94 95 ExceptionCode ec; 96 String localName, prefix; 97 if (!Document::parseQualifiedName(m_tagName, prefix, localName, ec)) 98 return; 99 QualifiedName qTypeOfBlock(prefix, localName, xhtmlNamespaceURI); 100 101 Node* refNode = enclosingBlockFlowElement(endingSelection().visibleStart()); 102 if (refNode->hasTagName(qTypeOfBlock)) 103 // We're already in a block with the format we want, so we don't have to do anything 104 return; 105 106 VisiblePosition paragraphStart = startOfParagraph(endingSelection().visibleStart()); 107 VisiblePosition paragraphEnd = endOfParagraph(endingSelection().visibleStart()); 108 VisiblePosition blockStart = startOfBlock(endingSelection().visibleStart()); 109 VisiblePosition blockEnd = endOfBlock(endingSelection().visibleStart()); 110 RefPtr<Element> blockNode = createHTMLElement(document(), m_tagName); 111 RefPtr<Element> placeholder = createBreakElement(document()); 112 113 Node* root = endingSelection().start().node()->rootEditableElement(); 114 if (validBlockTag(refNode->nodeName().lower()) && 115 paragraphStart == blockStart && paragraphEnd == blockEnd && 116 refNode != root && !root->isDescendantOf(refNode)) 117 // Already in a valid block tag that only contains the current paragraph, so we can swap with the new tag 118 insertNodeBefore(blockNode, refNode); 119 else { 120 // Avoid inserting inside inline elements that surround paragraphStart with upstream(). 121 // This is only to avoid creating bloated markup. 122 insertNodeAt(blockNode, paragraphStart.deepEquivalent().upstream()); 123 } 124 appendNode(placeholder, blockNode); 125 126 VisiblePosition destination(Position(placeholder.get(), 0)); 127 if (paragraphStart == paragraphEnd && !lineBreakExistsAtVisiblePosition(paragraphStart)) { 128 setEndingSelection(destination); 129 return; 130 } 131 moveParagraph(paragraphStart, paragraphEnd, destination, true, false); 132 } 133 134 } 135