summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp119
-rw-r--r--WebCore/editing/ApplyStyleCommand.h1
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp177
-rw-r--r--WebCore/editing/CompositeEditCommand.h3
-rw-r--r--WebCore/editing/EditorCommand.cpp4
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp115
-rw-r--r--WebCore/editing/IndentOutdentCommand.h5
-rw-r--r--WebCore/editing/ReplaceNodeWithSpanCommand.cpp4
-rw-r--r--WebCore/editing/SelectionController.cpp2
-rw-r--r--WebCore/editing/VisibleSelection.cpp2
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();
}