diff options
Diffstat (limited to 'WebCore/editing')
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.cpp | 119 | ||||
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.h | 1 | ||||
-rw-r--r-- | WebCore/editing/CompositeEditCommand.cpp | 177 | ||||
-rw-r--r-- | WebCore/editing/CompositeEditCommand.h | 3 | ||||
-rw-r--r-- | WebCore/editing/EditorCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/IndentOutdentCommand.cpp | 115 | ||||
-rw-r--r-- | WebCore/editing/IndentOutdentCommand.h | 5 | ||||
-rw-r--r-- | WebCore/editing/ReplaceNodeWithSpanCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/VisibleSelection.cpp | 2 |
10 files changed, 266 insertions, 166 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index 83d0bd3..7a8f025 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -334,6 +334,38 @@ static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID setTextDecorationProperty(style, newTextDecoration.get(), propertID); } +static bool fontWeightIsBold(CSSStyleDeclaration* style) +{ + ASSERT(style); + RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight); + + if (!fontWeight) + return false; + if (!fontWeight->isPrimitiveValue()) + return false; + + // Because b tag can only bold text, there are only two states in plain html: bold and not bold. + // Collapse all other values to either one of these two states for editing purposes. + switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) { + case CSSValue100: + case CSSValue200: + case CSSValue300: + case CSSValue400: + case CSSValue500: + case CSSValueNormal: + return false; + case CSSValueBold: + case CSSValue600: + case CSSValue700: + case CSSValue800: + case CSSValue900: + return true; + } + + ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter + return false; // Make compiler happy +} + RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDeclaration* style, CSSComputedStyleDeclaration* computedStyle) { ASSERT(style); @@ -345,6 +377,9 @@ RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDecla diffTextDecorations(result.get(), CSSPropertyTextDecoration, computedTextDecorationsInEffect.get()); diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, computedTextDecorationsInEffect.get()); + if (fontWeightIsBold(result.get()) == fontWeightIsBold(computedStyle)) + result->removeProperty(CSSPropertyFontWeight); + return result; } @@ -352,7 +387,6 @@ RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDecla // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. // FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties static const int editingStyleProperties[] = { - CSSPropertyBackgroundColor, // CSS inheritable properties CSSPropertyBorderCollapse, CSSPropertyColor, @@ -1079,6 +1113,23 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl } } +bool ApplyStyleCommand::shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const +{ + // Honor text-decorations-in-effect + RefPtr<CSSValue> textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + if (!textDecorationsToApply || !textDecorationsToApply->isValueList()) + textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyTextDecoration); + + // When there is no text decorations to apply, remove any one of u, s, & strike + if (!textDecorationsToApply || !textDecorationsToApply->isValueList()) + return true; + + // Remove node if it implicitly adds style not present in styleToApply + CSSValueList* valueList = static_cast<CSSValueList*>(textDecorationsToApply.get()); + RefPtr<CSSPrimitiveValue> value = CSSPrimitiveValue::createIdentifier(textDecorationAddedByTag); + return !valueList->hasValue(value.get()); +} + // This function maps from styling tags to CSS styles. Used for knowing which // styling tags should be removed when toggling styles. bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style) @@ -1092,36 +1143,25 @@ bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle( case CSSPropertyFontWeight: // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag)) - return true; + return !equalIgnoringCase(property.value()->cssText(), "bold") || !elem->hasChildNodes(); break; case CSSPropertyVerticalAlign: - if (elem->hasLocalName(subTag) || elem->hasLocalName(supTag)) - return true; + if (elem->hasLocalName(subTag)) + return !equalIgnoringCase(property.value()->cssText(), "sub") || !elem->hasChildNodes(); + if (elem->hasLocalName(supTag)) + return !equalIgnoringCase(property.value()->cssText(), "sup") || !elem->hasChildNodes(); break; case CSSPropertyFontStyle: // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag)) - return true; + return !equalIgnoringCase(property.value()->cssText(), "italic") || !elem->hasChildNodes(); break; case CSSPropertyTextDecoration: case CSSPropertyWebkitTextDecorationsInEffect: - ASSERT(property.value()); - if (property.value()->isValueList()) { - CSSValueList* valueList = static_cast<CSSValueList*>(property.value()); - DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline))); - DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough))); - // Because style is new style to be applied, we delete element only if the element is not used in style. - if (!valueList->hasValue(underline.get()) && elem->hasLocalName(uTag)) - return true; - if (!valueList->hasValue(lineThrough.get()) && (elem->hasLocalName(strikeTag) || elem->hasLocalName(sTag))) - return true; - } else { - // If the value is NOT a list, then it must be "none", in which case we should remove all text decorations. - ASSERT(property.value()->cssText() == "none"); - if (elem->hasLocalName(uTag) || elem->hasLocalName(strikeTag) || elem->hasLocalName(sTag)) - return true; - } - break; + if (elem->hasLocalName(uTag)) + return shouldRemoveTextDecorationTag(style, CSSValueUnderline) || !elem->hasChildNodes(); + else if (elem->hasLocalName(sTag) || elem->hasTagName(strikeTag)) + return shouldRemoveTextDecorationTag(style,CSSValueLineThrough) || !elem->hasChildNodes(); } } return false; @@ -1307,19 +1347,18 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl if (!style || style->cssText().isEmpty()) return; - if (node->isTextNode()) { - RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document()); - surroundNodeRangeWithElement(node, node, styleSpan.get()); - node = styleSpan.get(); - } - - if (!node->isElementNode()) - return; + StyleChange styleChange(style, Position(node, 0)); + if (styleChange.cssStyle().length()) { + if (node->isTextNode()) { + RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document()); + surroundNodeRangeWithElement(node, node, styleSpan.get()); + node = styleSpan.get(); + } - HTMLElement *element = static_cast<HTMLElement *>(node); + if (!node->isElementNode()) + return; - StyleChange styleChange(style, Position(element, 0)); - if (styleChange.cssStyle().length()) { + HTMLElement *element = static_cast<HTMLElement *>(node); String cssText = styleChange.cssStyle(); CSSMutableStyleDeclaration *decl = element->inlineStyleDecl(); if (decl) @@ -1679,6 +1718,20 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endN break; node = next; } + + Node* nextSibling = element->nextSibling(); + Node* previousSibling = element->previousSibling(); + if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable() + && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling))) + mergeIdenticalElements(element, static_cast<Element*>(nextSibling)); + + if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) { + Node* mergedElement = previousSibling->nextSibling(); + if (mergedElement->isElementNode() && mergedElement->isContentEditable() + && areIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement))) + mergeIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement)); + } + // FIXME: We should probably call updateStartEnd if the start or end was in the node // range so that the endingSelection() is canonicalized. See the comments at the end of // VisibleSelection::validate(). diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h index 29aa483..2804604 100644 --- a/WebCore/editing/ApplyStyleCommand.h +++ b/WebCore/editing/ApplyStyleCommand.h @@ -62,6 +62,7 @@ private: CSSMutableStyleDeclaration* style() const { return m_style.get(); } // style-removal helpers + bool shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const; bool implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement*, CSSMutableStyleDeclaration*); void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&); void removeHTMLFontStyle(CSSMutableStyleDeclaration*, HTMLElement*); diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 0496a8f..1617be8 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -20,7 +20,7 @@ * 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. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" @@ -71,7 +71,7 @@ namespace WebCore { using namespace HTMLNames; -CompositeEditCommand::CompositeEditCommand(Document *document) +CompositeEditCommand::CompositeEditCommand(Document *document) : EditCommand(document) { } @@ -396,7 +396,7 @@ void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) Node* node = position.node(); if (!node || !node->isTextNode()) return; - Text* textNode = static_cast<Text*>(node); + Text* textNode = static_cast<Text*>(node); if (textNode->length() == 0) return; @@ -739,6 +739,129 @@ void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown() setEndingSelection(originalSelection); } +// Clone the paragraph between start and end under blockElement, +// preserving the hierarchy up to outerNode. + +void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement) +{ + // First we clone the outerNode + + RefPtr<Node> lastNode = outerNode->cloneNode(isTableElement(outerNode)); + appendNode(lastNode, blockElement); + + if (start.node() != outerNode) { + Vector<RefPtr<Node> > ancestors; + + // Insert each node from innerNode to outerNode (excluded) in a list. + for (Node* n = start.node(); n && n != outerNode; n = n->parentNode()) + ancestors.append(n); + + // Clone every node between start.node() and outerBlock. + + for (size_t i = ancestors.size(); i != 0; --i) { + Node* item = ancestors[i - 1].get(); + RefPtr<Node> child = item->cloneNode(isTableElement(item)); + appendNode(child, static_cast<Element *>(lastNode.get())); + lastNode = child.release(); + } + } + + // Handle the case of paragraphs with more than one node, + // cloning all the siblings until end.node() is reached. + + if (start.node() != end.node()) { + for (Node* n = start.node()->nextSibling(); n != NULL; n = n->nextSibling()) { + RefPtr<Node> clonedNode = n->cloneNode(true); + insertNodeAfter(clonedNode, lastNode); + lastNode = clonedNode.release(); + if (n == end.node()) + break; + } + } +} + + +// There are bugs in deletion when it removes a fully selected table/list. +// It expands and removes the entire table/list, but will let content +// before and after the table/list collapse onto one line. +// Deleting a paragraph will leave a placeholder. Remove it (and prune +// empty or unrendered parents). + +void CompositeEditCommand::cleanupAfterDeletion() +{ + VisiblePosition caretAfterDelete = endingSelection().visibleStart(); + if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { + // Note: We want the rightmost candidate. + Position position = caretAfterDelete.deepEquivalent().downstream(); + Node* node = position.node(); + // Normally deletion will leave a br as a placeholder. + if (node->hasTagName(brTag)) + removeNodeAndPruneAncestors(node); + // If the selection to move was empty and in an empty block that + // doesn't require a placeholder to prop itself open (like a bordered + // div or an li), remove it during the move (the list removal code + // expects this behavior). + else if (isBlock(node)) + removeNodeAndPruneAncestors(node); + else if (lineBreakExistsAtPosition(position)) { + // There is a preserved '\n' at caretAfterDelete. + // We can safely assume this is a text node. + Text* textNode = static_cast<Text*>(node); + if (textNode->length() == 1) + removeNodeAndPruneAncestors(node); + else + deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); + } + } +} + +// This is a version of moveParagraph that preserves style by keeping the original markup +// It is currently used only by IndentOutdentCommand but it is meant to be used in the +// future by several other commands such as InsertList and the align commands. +// The blockElement parameter is the element to move the paragraph to, +// outerNode is the top element of the paragraph hierarchy. + +void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode) +{ + ASSERT(outerNode); + ASSERT(blockElement); + + VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); + VisiblePosition afterParagraph(endOfParagraphToMove.next()); + + // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. + // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. + Position start = startOfParagraphToMove.deepEquivalent().downstream(); + Position end = endOfParagraphToMove.deepEquivalent().upstream(); + + cloneParagraphUnderNewElement(start, end, outerNode, blockElement); + + setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); + deleteSelection(false, false, false, false); + + // There are bugs in deletion when it removes a fully selected table/list. + // It expands and removes the entire table/list, but will let content + // before and after the table/list collapse onto one line. + + cleanupAfterDeletion(); + + // Add a br if pruning an empty block level element caused a collapse. For example: + // foo^ + // <div>bar</div> + // baz + // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would + // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. + // Must recononicalize these two VisiblePositions after the pruning above. + beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); + afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); + + if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { + // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. + insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); + } +} + + // This moves a paragraph preserving its style. void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) { @@ -784,7 +907,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap VisiblePosition afterParagraph(endOfParagraphToMove.next()); // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. - // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. + // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. Position start = startOfParagraphToMove.deepEquivalent().downstream(); Position end = endOfParagraphToMove.deepEquivalent().upstream(); @@ -793,7 +916,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap Position endRangeCompliant = rangeCompliantEquivalent(end); RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset()); - // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It + // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It // shouldn't matter though, since moved paragraphs will usually be quite small. RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ? createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "") : 0; @@ -813,42 +936,14 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap deleteSelection(false, false, false, false); ASSERT(destination.deepEquivalent().node()->inDocument()); - - // There are bugs in deletion when it removes a fully selected table/list. - // It expands and removes the entire table/list, but will let content - // before and after the table/list collapse onto one line. - - // Deleting a paragraph will leave a placeholder. Remove it (and prune - // empty or unrendered parents). - VisiblePosition caretAfterDelete = endingSelection().visibleStart(); - if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { - // Note: We want the rightmost candidate. - Position position = caretAfterDelete.deepEquivalent().downstream(); - Node* node = position.node(); - // Normally deletion will leave a br as a placeholder. - if (node->hasTagName(brTag)) - removeNodeAndPruneAncestors(node); - // If the selection to move was empty and in an empty block that - // doesn't require a placeholder to prop itself open (like a bordered - // div or an li), remove it during the move (the list removal code - // expects this behavior). - else if (isBlock(node)) - removeNodeAndPruneAncestors(node); - else if (lineBreakExistsAtVisiblePosition(caretAfterDelete)) { - // There is a preserved '\n' at caretAfterDelete. - Text* textNode = static_cast<Text*>(node); - if (textNode->length() == 1) - removeNodeAndPruneAncestors(node); - else - deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); - } - } - // Add a br if pruning an empty block level element caused a collapse. For example: + cleanupAfterDeletion(); + + // Add a br if pruning an empty block level element caused a collapse. For example: // foo^ // <div>bar</div> // baz - // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would + // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. // Must recononicalize these two VisiblePositions after the pruning above. beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); @@ -912,7 +1007,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() removeNodePreservingChildren(listNode->parentNode()); newBlock = createListItemElement(document()); } - // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. + // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) newBlock = createListItemElement(document()); } @@ -971,7 +1066,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() // to hold the caret before the highest blockquote. insertNodeBefore(br, highestBlockquote); VisiblePosition atBR(Position(br.get(), 0)); - // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert + // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert // a second one. if (!isStartOfParagraph(atBR)) insertNodeBefore(createBreakElement(document()), br); @@ -1002,7 +1097,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() return true; } -// Operations use this function to avoid inserting content into an anchor when at the start or the end of +// Operations use this function to avoid inserting content into an anchor when at the start or the end of // that anchor, as in NSTextView. // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how // the caret was made. @@ -1022,7 +1117,7 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi if (enclosingAnchor && !isBlock(enclosingAnchor)) { VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor)); VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor)); - // If visually just after the anchor, insert *inside* the anchor unless it's the last + // If visually just after the anchor, insert *inside* the anchor unless it's the last // VisiblePosition in the document, to match NSTextView. if (visiblePos == lastInAnchor) { // Make sure anchors are pushed down before avoiding them so that we don't diff --git a/WebCore/editing/CompositeEditCommand.h b/WebCore/editing/CompositeEditCommand.h index 2c6403e..0cceaaa 100644 --- a/WebCore/editing/CompositeEditCommand.h +++ b/WebCore/editing/CompositeEditCommand.h @@ -102,6 +102,9 @@ protected: void moveParagraph(const VisiblePosition&, const VisiblePosition&, const VisiblePosition&, bool preserveSelection = false, bool preserveStyle = true); void moveParagraphs(const VisiblePosition&, const VisiblePosition&, const VisiblePosition&, bool preserveSelection = false, bool preserveStyle = true); + void moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode); + void cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement); + void cleanupAfterDeletion(); bool breakOutOfEmptyListItem(); bool breakOutOfEmptyMailBlockquotedParagraph(); diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index abd0174..3379b3c 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -1291,10 +1291,10 @@ static String valueForeColor(Frame* frame, Event*) // Map of functions +struct CommandEntry { const char* name; EditorInternalCommand command; }; + static const CommandMap& createCommandMap() { - struct CommandEntry { const char* name; EditorInternalCommand command; }; - static const CommandEntry commands[] = { { "AlignCenter", { executeJustifyCenter, supportedFromMenuOrKeyBinding, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "AlignJustified", { executeJustifyFull, supportedFromMenuOrKeyBinding, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp index 308ba74..808a2f8 100644 --- a/WebCore/editing/IndentOutdentCommand.cpp +++ b/WebCore/editing/IndentOutdentCommand.cpp @@ -33,7 +33,6 @@ #include "InsertLineBreakCommand.h" #include "InsertListCommand.h" #include "Range.h" -#include "DocumentFragment.h" #include "SplitElementCommand.h" #include "TextIterator.h" #include "htmlediting.h" @@ -81,14 +80,15 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu // FIXME: we need to deal with the case where there is no li (malformed HTML) if (!selectedListItem->hasTagName(liTag)) return false; - + // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>. Should we? Element* previousList = selectedListItem->previousElementSibling(); Element* nextList = selectedListItem->nextElementSibling(); RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false); insertNodeBefore(newList, selectedListItem); - appendParagraphIntoNode(visiblePositionBeforeNode(selectedListItem), visiblePositionAfterNode(selectedListItem), newList.get()); + + moveParagraphWithClones(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, newList.get(), selectedListItem); if (canMergeLists(previousList, newList.get())) mergeIdenticalElements(previousList, newList); @@ -98,87 +98,44 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu return true; } -void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& startOfCurrentParagraph, const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& targetBlockquote, Node* nodeToSplitTo) +void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurrentParagraph, const VisiblePosition& endOfNextParagraph, RefPtr<Element>& targetBlockquote) { Node* enclosingCell = 0; + Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent(); + enclosingCell = enclosingNodeOfType(start, &isTableCell); + Node* nodeToSplitTo; + if (enclosingCell) + nodeToSplitTo = enclosingCell; + else if (enclosingList(start.node())) + nodeToSplitTo = enclosingBlock(start.node()); + else + nodeToSplitTo = editableRootForPosition(start); + + RefPtr<Node> outerBlock = splitTreeToNode(start.node(), nodeToSplitTo); + if (!targetBlockquote) { - // Create a new blockquote and insert it as a child of the enclosing block element. We accomplish + // 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()); - if (isTableCell(nodeToSplitTo)) - enclosingCell = nodeToSplitTo; - RefPtr<Node> startOfNewBlock = splitTreeToNode(startOfCurrentParagraph.deepEquivalent().node(), nodeToSplitTo); - insertNodeBefore(targetBlockquote, startOfNewBlock); + insertNodeBefore(targetBlockquote, outerBlock); } - VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); - appendParagraphIntoNode(startOfCurrentParagraph, endOfCurrentParagraph, targetBlockquote.get()); - + 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; } -// Enclose all nodes between start and end by newParent, which is a sibling node of nodes between start and end -// FIXME: moveParagraph is overly complicated. We need to clean up moveParagraph so that it uses appendParagraphIntoNode -// or prepare more specialized functions and delete moveParagraph -void IndentOutdentCommand::appendParagraphIntoNode(const VisiblePosition& start, const VisiblePosition& end, Node* newParent) -{ - ASSERT(newParent); - ASSERT(newParent->isContentEditable()); - ASSERT(isStartOfParagraph(start) && isEndOfParagraph(end)); - - Position endOfParagraph = end.deepEquivalent().downstream(); - Node* insertionPoint = newParent->lastChild();// Remember the place to put br later - // Look for the beginning of the last paragraph in newParent - Node* startOfLastParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node(); - if (startOfLastParagraph && !startOfLastParagraph->isDescendantOf(newParent)) - startOfLastParagraph = 0; - - // Extend the range so that we can append wrapping nodes as well if they're containd within the paragraph - ExceptionCode ec = 0; - RefPtr<Range> selectedRange = createRange(document(), start, end, ec); - RefPtr<Range> extendedRange = extendRangeToWrappingNodes(selectedRange, selectedRange.get(), newParent->parentNode()); - newParent->appendChild(extendedRange->extractContents(ec), ec); - - // If the start of paragraph didn't change by appending nodes, we should insert br to seperate the paragraphs. - Node* startOfNewParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node(); - if (startOfNewParagraph == startOfLastParagraph) { - if (insertionPoint) - newParent->insertBefore(createBreakElement(document()), insertionPoint->nextSibling(), ec); - else - newParent->appendChild(createBreakElement(document()), ec); - } - - // Remove unnecessary br from the place where we moved the paragraph from - removeUnnecessaryLineBreakAt(endOfParagraph); -} - -void IndentOutdentCommand::removeUnnecessaryLineBreakAt(const Position& endOfParagraph) -{ - // If there is something in this paragraph, then don't remove br. - if (!isStartOfParagraph(endOfParagraph) || !isEndOfParagraph(endOfParagraph)) - return; - - // We only care about br at the end of paragraph - Node* br = endOfParagraph.node(); - Node* parentNode = br->parentNode(); - - // If the node isn't br or the parent node is empty, then don't remove. - if (!br->hasTagName(brTag) || isVisiblyAdjacent(positionInParentBeforeNode(parentNode), positionInParentAfterNode(parentNode))) - return; - - removeNodeAndPruneAncestors(br); -} - void IndentOutdentCommand::indentRegion() { VisibleSelection selection = selectionForParagraphIteration(endingSelection()); VisiblePosition startOfSelection = selection.visibleStart(); VisiblePosition endOfSelection = selection.visibleEnd(); - RefPtr<Range> selectedRange = selection.firstRange(); + int startIndex = indexForVisiblePosition(startOfSelection); + int endIndex = indexForVisiblePosition(endOfSelection); ASSERT(!startOfSelection.isNull()); ASSERT(!endOfSelection.isNull()); @@ -203,24 +160,8 @@ void IndentOutdentCommand::indentRegion() VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); if (tryIndentingAsListItem(endOfCurrentParagraph)) blockquoteForNextIndent = 0; - else { - VisiblePosition startOfCurrentParagraph = startOfParagraph(endOfCurrentParagraph); - Node* blockNode = enclosingBlock(endOfCurrentParagraph.deepEquivalent().node()->parentNode()); - // extend the region so that it contains all the ancestor blocks within the selection - ExceptionCode ec; - Element* unsplittableNode = unsplittableElementForPosition(endOfCurrentParagraph.deepEquivalent()); - RefPtr<Range> originalRange = createRange(document(), endOfCurrentParagraph, endOfCurrentParagraph, ec); - RefPtr<Range> extendedRange = extendRangeToWrappingNodes(originalRange, selectedRange.get(), unsplittableNode); - if (originalRange != extendedRange) { - ExceptionCode ec = 0; - endOfCurrentParagraph = endOfParagraph(extendedRange->endPosition().previous()); - blockNode = enclosingBlock(extendedRange->commonAncestorContainer(ec)); - } - - endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); - indentIntoBlockquote(startOfCurrentParagraph, endOfCurrentParagraph, blockquoteForNextIndent, blockNode); - // blockquoteForNextIndent will be updated in the function - } + else + indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent); // 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()) { @@ -229,7 +170,13 @@ void IndentOutdentCommand::indentRegion() } endOfCurrentParagraph = endOfNextParagraph; } - + + 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::outdentParagraph() diff --git a/WebCore/editing/IndentOutdentCommand.h b/WebCore/editing/IndentOutdentCommand.h index d83128e..817b4c8 100644 --- a/WebCore/editing/IndentOutdentCommand.h +++ b/WebCore/editing/IndentOutdentCommand.h @@ -46,14 +46,11 @@ private: virtual void doApply(); virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; } - void appendParagraphIntoNode(const VisiblePosition& start, const VisiblePosition& end, Node* newParent); - void removeUnnecessaryLineBreakAt(const Position& endOfParagraph); - void indentRegion(); void outdentRegion(); void outdentParagraph(); bool tryIndentingAsListItem(const VisiblePosition&); - void indentIntoBlockquote(const VisiblePosition&, const VisiblePosition&, RefPtr<Element>& targetBlockquote, Node* nodeToSplitTo); + void indentIntoBlockquote(const VisiblePosition&, const VisiblePosition&, RefPtr<Element>&); EIndentType m_typeOfAction; int m_marginInPixels; diff --git a/WebCore/editing/ReplaceNodeWithSpanCommand.cpp b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp index 21ca924..0874201 100644 --- a/WebCore/editing/ReplaceNodeWithSpanCommand.cpp +++ b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp @@ -57,7 +57,9 @@ static void swapInNodePreservingAttributesAndChildren(Node* newNode, Node* nodeT parentNode->insertBefore(newNode, nodeToReplace, ec); ASSERT(!ec); - for (Node* child = nodeToReplace->firstChild(); child; child = child->nextSibling()) { + Node* nextChild; + for (Node* child = nodeToReplace->firstChild(); child; child = nextChild) { + nextChild = child->nextSibling(); newNode->appendChild(child, ec); ASSERT(!ec); } diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index 7d99916..00672f2 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -725,6 +725,8 @@ bool SelectionController::modify(EAlteration alter, int verticalDistance, bool u if (userTriggered) m_frame->setSelectionGranularity(CharacterGranularity); + m_lastChangeWasHorizontalExtension = alter == EXTEND; + return true; } diff --git a/WebCore/editing/VisibleSelection.cpp b/WebCore/editing/VisibleSelection.cpp index 8adfd71..206de86 100644 --- a/WebCore/editing/VisibleSelection.cpp +++ b/WebCore/editing/VisibleSelection.cpp @@ -237,7 +237,7 @@ void VisibleSelection::appendTrailingWhitespace() for (; charIt.length(); charIt.advance(1)) { UChar c = charIt.characters()[0]; - if (!isSpaceOrNewline(c) && c != noBreakSpace) + if (!isSpaceOrNewline(c) && c != noBreakSpace || c == '\n') break; m_end = charIt.range()->endPosition(); } |