-static int maxRangeOffset(NodeImpl *n)
-{
- if (DOM::offsetInCharacters(n->nodeType()))
- return n->maxOffset();
-
- if (n->isElementNode())
- return n->childNodeCount();
-
- return 1;
-}
-
-static int maxDeepOffset(NodeImpl *n)
-{
- if (n->isAtomicNode())
- return n->caretMaxOffset();
-
- if (n->isElementNode())
- return n->childNodeCount();
-
- return 1;
-}
-
-static void debugPosition(const char *prefix, const Position &pos)
-{
- if (!prefix)
- prefix = "";
- if (pos.isNull())
- LOG(Editing, "%s <null>", prefix);
- else
- LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
-}
-
-static void debugNode(const char *prefix, const NodeImpl *node)
-{
- if (!prefix)
- prefix = "";
- if (!node)
- LOG(Editing, "%s <null>", prefix);
- else
- LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
-}
-
-//------------------------------------------------------------------------------------------
-// StyleChange
-
-StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
- : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
-{
- init(style, Position());
-}
-
-StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
- : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
-{
- init(style, position);
-}
-
-void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
-{
- style->ref();
- CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
- mutableStyle->ref();
- style->deref();
-
- QString styleText("");
-
- QValueListConstIterator<CSSProperty> end;
- for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
- const CSSProperty *property = &*it;
-
- // If position is empty or the position passed in already has the
- // style, just move on.
- if (position.isNotNull() && currentlyHasStyle(position, property))
- continue;
-
- // If needed, figure out if this change is a legacy HTML style change.
- if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
- continue;
-
- // Add this property
-
- if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
- // we have to special-case text decorations
- CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
- styleText += alteredProperty.cssText().string();
- } else {
- styleText += property->cssText().string();
- }
- }
-
- mutableStyle->deref();
-
- // Save the result for later
- m_cssStyle = styleText.stripWhiteSpace();
-}
-
-StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
-{
- return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
-}
-
-bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
-{
- if (!property || !property->value()) {
- return false;
- }
-
- DOMString valueText(property->value()->cssText());
- switch (property->id()) {
- case CSS_PROP_FONT_WEIGHT:
- if (strcasecmp(valueText, "bold") == 0) {
- m_applyBold = true;
- return true;
- }
- break;
- case CSS_PROP_FONT_STYLE:
- if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
- m_applyItalic = true;
- return true;
- }
- break;
- case CSS_PROP_COLOR: {
- QColor color(CSSParser::parseColor(valueText));
- m_applyFontColor = color.name();
- return true;
- }
- case CSS_PROP_FONT_FAMILY:
- m_applyFontFace = valueText;
- return true;
- case CSS_PROP_FONT_SIZE:
- if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
- CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
- float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
- if (number <= 9)
- m_applyFontSize = "1";
- else if (number <= 10)
- m_applyFontSize = "2";
- else if (number <= 13)
- m_applyFontSize = "3";
- else if (number <= 16)
- m_applyFontSize = "4";
- else if (number <= 18)
- m_applyFontSize = "5";
- else if (number <= 24)
- m_applyFontSize = "6";
- else
- m_applyFontSize = "7";
- // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
- // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
- // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
- return false;
- }
- else {
- // Can't make sense of the number. Put no font size.
- return true;
- }
- }
- return false;
-}
-
-bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
-{
- ASSERT(pos.isNotNull());
- CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
- ASSERT(style);
- style->ref();
- CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
- style->deref();
- if (!value)
- return false;
- value->ref();
- bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
- value->deref();
- return result;
-}
-
-//------------------------------------------------------------------------------------------
-// CompositeEditCommand
-
-CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
- : EditCommand(document)
-{
-}
-
-void CompositeEditCommand::doUnapply()
-{
- if (m_cmds.count() == 0) {
- return;
- }
-
- for (int i = m_cmds.count() - 1; i >= 0; --i)
- m_cmds[i]->unapply();
-
- setState(NotApplied);
-}
-
-void CompositeEditCommand::doReapply()
-{
- if (m_cmds.count() == 0) {
- return;
- }
-
- for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
- (*it)->reapply();
-
- setState(Applied);
-}
-
-//
-// sugary-sweet convenience functions to help create and apply edit commands in composite commands
-//
-void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
-{
- cmd.setStartingSelection(endingSelection());
- cmd.setEndingSelection(endingSelection());
- cmd.setParent(this);
- cmd.apply();
- m_cmds.append(cmd);
-}
-
-void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
-{
- EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::insertParagraphSeparator()
-{
- EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
-{
- ASSERT(refChild->id() != ID_BODY);
- EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
-{
- ASSERT(refChild->id() != ID_BODY);
- if (refChild->parentNode()->lastChild() == refChild) {
- appendNode(insertChild, refChild->parentNode());
- }
- else {
- ASSERT(refChild->nextSibling());
- insertNodeBefore(insertChild, refChild->nextSibling());
- }
-}
-
-void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
-{
- if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
- NodeImpl *child = refChild->firstChild();
- for (long i = 0; child && i < offset; i++)
- child = child->nextSibling();
- if (child)
- insertNodeBefore(insertChild, child);
- else
- appendNode(insertChild, refChild);
- }
- else if (refChild->caretMinOffset() >= offset) {
- insertNodeBefore(insertChild, refChild);
- }
- else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
- splitTextNode(static_cast<TextImpl *>(refChild), offset);
- insertNodeBefore(insertChild, refChild);
- }
- else {
- insertNodeAfter(insertChild, refChild);
- }
-}
-
-void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
-{
- EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
-{
- if (isTableStructureNode(node) || node == node->rootEditableElement()) {
- // Do not remove an element of table structure; remove its contents.
- // Likewise for the root editable element.
- NodeImpl *child = node->firstChild();
- while (child) {
- NodeImpl *remove = child;
- child = child->nextSibling();
- removeFullySelectedNode(remove);
- }
- }
- else {
- removeNode(node);
- }
-}
-
-void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
-{
- NodeImpl *nodeToRemove = node->childNode(from);
- for (int i = from; i < to; i++) {
- ASSERT(nodeToRemove);
- NodeImpl *next = nodeToRemove->nextSibling();
- removeNode(nodeToRemove);
- nodeToRemove = next;
- }
-}
-
-void CompositeEditCommand::removeNode(NodeImpl *removeChild)
-{
- EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
-{
- EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
-{
- EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
-{
- EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
-{
- EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
-{
- EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
-{
- EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
-{
- EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
-{
- InsertTextCommand *impl = new InsertTextCommand(document());
- EditCommandPtr cmd(impl);
- applyCommandToComposite(cmd);
- impl->input(text, selectInsertedText);
-}
-
-void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
-{
- EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
-{
- EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
-{
- EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
- applyCommandToComposite(deleteCommand);
- EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
- applyCommandToComposite(insertCommand);
-}
-
-void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
-{
- if (endingSelection().isRange()) {
- EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
- applyCommandToComposite(cmd);
- }
-}
-
-void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
-{
- if (selection.isRange()) {
- EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
- applyCommandToComposite(cmd);
- }
-}
-
-void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
-{
- EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
-{
- DOMString value = element->getAttribute(attribute);
- if (value.isEmpty())
- return;
- EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
-{
- EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
- applyCommandToComposite(cmd);
-}
-
-void CompositeEditCommand::rebalanceWhitespace()
-{
- Selection selection = endingSelection();
- if (selection.isCaretOrRange()) {
- EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
- applyCommandToComposite(startCmd);
- if (selection.isRange()) {
- EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
- applyCommandToComposite(endCmd);
- }
- }
-}
-
-void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
-{
- if (!textNode || !textNode->renderer() || start >= end)
- return;
-
- RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
- InlineTextBox *box = textRenderer->firstTextBox();
- if (!box) {
- // whole text node is empty
- removeNode(textNode);
- return;
- }
-
- long length = textNode->length();
- if (start >= length || end > length)
- return;
-
- int removed = 0;
- InlineTextBox *prevBox = 0;
- DOMStringImpl *str = 0;
-
- // This loop structure works to process all gaps preceding a box,
- // and also will look at the gap after the last box.
- while (prevBox || box) {
- int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
- if (end < gapStart)
- // No more chance for any intersections
- break;
-
- int gapEnd = box ? box->m_start : length;
- bool indicesIntersect = start <= gapEnd && end >= gapStart;
- int gapLen = gapEnd - gapStart;
- if (indicesIntersect && gapLen > 0) {
- gapStart = kMax(gapStart, start);
- gapEnd = kMin(gapEnd, end);
- if (!str) {
- str = textNode->string()->substring(start, end - start);
- str->ref();
- }
- // remove text in the gap
- str->remove(gapStart - start - removed, gapLen);
- removed += gapLen;
- }
-
- prevBox = box;
- if (box)
- box = box->nextTextBox();
- }
-
- if (str) {
- // Replace the text between start and end with our pruned version.
- if (str->l > 0) {
- replaceTextInNode(textNode, start, end - start, str);
- }
- else {
- // Assert that we are not going to delete all of the text in the node.
- // If we were, that should have been done above with the call to
- // removeNode and return.
- ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
- deleteTextFromNode(textNode, start, end - start);
- }
- str->deref();
- }
-}
-
-void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
-{
- if (start.isNull() || end.isNull())
- return;
-
- if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
- return;
-
- NodeImpl *node = start.node();
- while (node) {
- NodeImpl *next = node->traverseNextNode();
-
- if (node->isTextNode()) {
- TextImpl *textNode = static_cast<TextImpl *>(node);
- bool isStartNode = node == start.node();
- bool isEndNode = node == end.node();
- int startOffset = isStartNode ? start.offset() : 0;
- int endOffset = isEndNode ? end.offset() : textNode->length();
- deleteInsignificantText(textNode, startOffset, endOffset);
- }
-
- if (node == end.node())
- break;
- node = next;
- }
-}
-
-void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
-{
- Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
- deleteInsignificantText(pos, end);
-}
-
-NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
-{
- if (!node)
- return NULL;
-
- ASSERT(node->renderer() && node->renderer()->isBlockFlow());
-
- NodeImpl *placeholder = createBlockPlaceholderElement(document());
- appendNode(placeholder, node);
- return placeholder;
-}
-
-NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
-{
- if (pos.isNull())
- return NULL;
-
- ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow());
-
- NodeImpl *placeholder = createBlockPlaceholderElement(document());
- insertNodeAt(placeholder, pos.node(), pos.offset());
- return placeholder;
-}
-
-NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
-{
- if (!node)
- return false;
-
- document()->updateLayout();
-
- RenderObject *renderer = node->renderer();
- if (!renderer || !renderer->isBlockFlow())
- return false;
-
- // append the placeholder to make sure it follows
- // any unrendered blocks
- if (renderer->height() == 0) {
- return appendBlockPlaceholder(node);
- }
-
- return NULL;
-}
-
-bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
-{
- NodeImpl *placeholder = findBlockPlaceholder(node);
- if (placeholder) {
- removeNode(placeholder);
- return true;
- }
- return false;
-}
-
-NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
-{
- if (!node)
- return 0;
-
- document()->updateLayout();
-
- RenderObject *renderer = node->renderer();
- if (!renderer || !renderer->isBlockFlow())
- return 0;
-
- for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
- if (checkMe->isElementNode()) {
- ElementImpl *element = static_cast<ElementImpl *>(checkMe);
- if (element->enclosingBlockFlowElement() == node &&
- element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
- return element;
- }
- }
- }
-
- return 0;
-}
-
-void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
-{
- if (pos.isNull())
- return;
-
- document()->updateLayout();
-
- VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
- VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
- VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
- Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream();
- Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream();
-
- // Perform some checks to see if we need to perform work in this function.
- if (paragraphStart.node()->isBlockFlow()) {
- if (paragraphEnd.node()->isBlockFlow()) {
- if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
- // If the paragraph end is a descendant of paragraph start, then we need to run
- // the rest of this function. If not, we can bail here.
- return;
- }
- }
- else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
- // The paragraph end is in another block that is an ancestor of the paragraph start.
- // We can bail as we have a full block to work with.
- ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
- return;
- }
- else if (isEndOfDocument(visibleParagraphEnd)) {
- // At the end of the document. We can bail here as well.
- return;
- }
- }
-
- // Create the block to insert. Most times, this will be a shallow clone of the block containing
- // the start of the selection (the start block), except for two cases:
- // 1) When the start block is a body element.
- // 2) When the start block is a mail blockquote and we are not in a position to insert
- // the new block as a peer of the start block. This prevents creating an unwanted
- // additional level of quoting.
- NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
- NodeImpl *newBlock = 0;
- if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
- newBlock = createDefaultParagraphElement(document());
- else
- newBlock = startBlock->cloneNode(false);
-
- NodeImpl *moveNode = paragraphStart.node();
- if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
- moveNode = moveNode->traverseNextNode();
- NodeImpl *endNode = paragraphEnd.node();
-
- if (paragraphStart.node()->id() == ID_BODY) {
- insertNodeAt(newBlock, paragraphStart.node(), 0);
- }
- else if (paragraphStart.node()->id() == ID_BR) {
- insertNodeAfter(newBlock, paragraphStart.node());
- }
- else {
- insertNodeBefore(newBlock, paragraphStart.upstream().node());
- }
-
- while (moveNode && !moveNode->isBlockFlow()) {
- NodeImpl *next = moveNode->traverseNextSibling();
- removeNode(moveNode);
- appendNode(moveNode, newBlock);
- if (moveNode == endNode)
- break;
- moveNode = next;
- }
-}
-
-static bool isSpecialElement(NodeImpl *n)
-{
- if (!n->isHTMLElement())
- return false;
-
- if (n->id() == ID_A && n->isLink())
- return true;
-
- if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
- return true;
-
- RenderObject *renderer = n->renderer();
-
- if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
- return true;
-
- if (renderer && renderer->style()->isFloating())
- return true;
-
- if (renderer && renderer->style()->position() != STATIC)
- return true;
-
- return false;
-}
-
-// This version of the function is meant to be called on positions in a document fragment,
-// so it does not check for a root editable element, it is assumed these nodes will be put
-// somewhere editable in the future
-static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
-{
- VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
-
- for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
- return false;
- if (isSpecialElement(n))
- return true;
- }
-
- return false;
-}
-
-static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
-{
- VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
-
- for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
- return false;
- if (n->rootEditableElement() == NULL)
- return false;
- if (isSpecialElement(n))
- return true;
- }
-
- return false;
-}
-
-static Position positionBeforeNode(NodeImpl *node)
-{
- return Position(node->parentNode(), node->nodeIndex());
-}
-
-static Position positionBeforeContainingSpecialElement(const Position& pos)
-{
- ASSERT(isFirstVisiblePositionInSpecialElement(pos));
-
- VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
-
- NodeImpl *outermostSpecialElement = NULL;
-
- for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
- break;
- if (n->rootEditableElement() == NULL)
- break;
- if (isSpecialElement(n))
- outermostSpecialElement = n;
- }
-
- ASSERT(outermostSpecialElement);
-
- Position result = positionBeforeNode(outermostSpecialElement);
- if (result.isNull() || !result.node()->rootEditableElement())
- return pos;
-
- return result;
-}
-
-static bool isLastVisiblePositionInSpecialElement(const Position& pos)
-{
- // make sure to get a range-compliant version of the position
- Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
-
- VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
-
- for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
- return false;
- if (n->rootEditableElement() == NULL)
- return false;
- if (isSpecialElement(n))
- return true;
- }
-
- return false;
-}
-
-static Position positionAfterNode(NodeImpl *node)
-{
- return Position(node->parentNode(), node->nodeIndex() + 1);
-}
-
-static Position positionAfterContainingSpecialElement(const Position& pos)
-{
- ASSERT(isLastVisiblePositionInSpecialElement(pos));
-
- // make sure to get a range-compliant version of the position
- Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
-
- VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
-
- NodeImpl *outermostSpecialElement = NULL;
-
- for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
- break;
- if (n->rootEditableElement() == NULL)
- break;
- if (isSpecialElement(n))
- outermostSpecialElement = n;
- }
-
- ASSERT(outermostSpecialElement);
-
- Position result = positionAfterNode(outermostSpecialElement);
- if (result.isNull() || !result.node()->rootEditableElement())
- return pos;
-
- return result;
-}
-
-static Position positionOutsideContainingSpecialElement(const Position &pos)
-{
- if (isFirstVisiblePositionInSpecialElement(pos)) {
- return positionBeforeContainingSpecialElement(pos);
- } else if (isLastVisiblePositionInSpecialElement(pos)) {
- return positionAfterContainingSpecialElement(pos);
- }
-
- return pos;
-}
-
-static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
-{
- if (isFirstVisiblePositionInSpecialElement(pos)) {
- return positionBeforeContainingSpecialElement(pos);
- }
-
- return pos;
-}
-
-static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
-{
- if (isLastVisiblePositionInSpecialElement(pos)) {
- return positionAfterContainingSpecialElement(pos);
- }
-
- return pos;
-}
-
-//==========================================================================================
-// Concrete commands
-//------------------------------------------------------------------------------------------
-// AppendNodeCommand
-
-AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
- : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
-{
- ASSERT(m_appendChild);
- m_appendChild->ref();
-
- ASSERT(m_parentNode);
- m_parentNode->ref();
-}
-
-AppendNodeCommand::~AppendNodeCommand()
-{
- ASSERT(m_appendChild);
- m_appendChild->deref();
-
- ASSERT(m_parentNode);
- m_parentNode->deref();
-}
-
-void AppendNodeCommand::doApply()
-{
- ASSERT(m_appendChild);
- ASSERT(m_parentNode);
-
- int exceptionCode = 0;
- m_parentNode->appendChild(m_appendChild, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void AppendNodeCommand::doUnapply()
-{
- ASSERT(m_appendChild);
- ASSERT(m_parentNode);
- ASSERT(state() == Applied);
-
- int exceptionCode = 0;
- m_parentNode->removeChild(m_appendChild, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand
-
-ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
- : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
-{
- ASSERT(m_style);
- m_style->ref();
-}
-
-ApplyStyleCommand::~ApplyStyleCommand()
-{
- ASSERT(m_style);
- m_style->deref();
-}
-
-void ApplyStyleCommand::doApply()
-{
- switch (m_propertyLevel) {
- case PropertyDefault: {
- // apply the block-centric properties of the style
- CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
- blockStyle->ref();
- applyBlockStyle(blockStyle);
- // apply any remaining styles to the inline elements
- // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
- if (blockStyle->length() < m_style->length()) {
- CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
- inlineStyle->ref();
- applyRelativeFontStyleChange(inlineStyle);
- blockStyle->diff(inlineStyle);
- applyInlineStyle(inlineStyle);
- inlineStyle->deref();
- }
- blockStyle->deref();
- break;
- }
- case ForceBlockProperties:
- // Force all properties to be applied as block styles.
- applyBlockStyle(m_style);
- break;
- }
-
- setEndingSelectionNeedsLayout();
-}
-
-EditAction ApplyStyleCommand::editingAction() const
-{
- return m_editingAction;
-}
-
-void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
-{
- // update document layout once before removing styles
- // so that we avoid the expense of updating before each and every call
- // to check a computed style
- document()->updateLayout();
-
- // get positions we want to use for applying style
- Position start(endingSelection().start());
- Position end(endingSelection().end());
-
- // remove current values, if any, of the specified styles from the blocks
- // NOTE: tracks the previous block to avoid repeated processing
- // Also, gather up all the nodes we want to process in a QPtrList before
- // doing anything. This averts any bugs iterating over these nodes
- // once you start removing and applying style.
- NodeImpl *beyondEnd = end.node()->traverseNextNode();
- QPtrList<NodeImpl> nodes;
- for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
- nodes.append(node);
-
- NodeImpl *prevBlock = 0;
- for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
- NodeImpl *block = it.current()->enclosingBlockFlowElement();
- if (block != prevBlock && block->isHTMLElement()) {
- removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
- prevBlock = block;
- }
- }
-
- // apply specified styles to the block flow elements in the selected range
- prevBlock = 0;
- for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
- NodeImpl *node = it.current();
- if (node->renderer()) {
- NodeImpl *block = node->enclosingBlockFlowElement();
- if (block != prevBlock) {
- addBlockStyleIfNeeded(style, node);
- prevBlock = block;
- }
- }
- }
-}
-
-#define NoFontDelta (0.0f)
-#define MinimumFontSize (0.1f)
-
-void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
-{
- if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
- // Explicit font size overrides any delta.
- style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
- return;
- }
-
- // Get the adjustment amount out of the style.
- CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
- if (!value)
- return;
- value->ref();
- float adjustment = NoFontDelta;
- if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
- CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
- if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
- // Only PX handled now. If we handle more types in the future, perhaps
- // a switch statement here would be more appropriate.
- adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
- }
- }
- style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
- value->deref();
- if (adjustment == NoFontDelta)
- return;
-
- // Adjust to the positions we want to use for applying style.
- Selection selection = endingSelection();
- Position start(selection.start().downstream());
- Position end(selection.end().upstream());
- if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
- Position swap = start;
- start = end;
- end = swap;
- }
-
- // Join up any adjacent text nodes.
- if (start.node()->isTextNode()) {
- joinChildTextNodes(start.node()->parentNode(), start, end);
- selection = endingSelection();
- start = selection.start();
- end = selection.end();
- }
- if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
- joinChildTextNodes(end.node()->parentNode(), start, end);
- selection = endingSelection();
- start = selection.start();
- end = selection.end();
- }
-
- // Split the start text nodes if needed to apply style.
- bool splitStart = splitTextAtStartIfNeeded(start, end);
- if (splitStart) {
- start = endingSelection().start();
- end = endingSelection().end();
- }
- bool splitEnd = splitTextAtEndIfNeeded(start, end);
- if (splitEnd) {
- start = endingSelection().start();
- end = endingSelection().end();
- }
-
- NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
- start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
- NodeImpl *startNode = start.node();
- if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
- startNode = startNode->traverseNextNode();
-
- // Store away font size before making any changes to the document.
- // This ensures that changes to one node won't effect another.
- QMap<const NodeImpl *,float> startingFontSizes;
- for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
- startingFontSizes.insert(node, computedFontSize(node));
-
- // These spans were added by us. If empty after font size changes, they can be removed.
- QPtrList<NodeImpl> emptySpans;
-
- NodeImpl *lastStyledNode = 0;
- for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
- HTMLElementImpl *elem = 0;
- if (node->isHTMLElement()) {
- // Only work on fully selected nodes.
- if (!nodeFullySelected(node, start, end))
- continue;
- elem = static_cast<HTMLElementImpl *>(node);
- }
- else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
- // Last styled node was not parent node of this text node, but we wish to style this
- // text node. To make this possible, add a style span to surround this text node.
- elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
- insertNodeBefore(elem, node);
- surroundNodeRangeWithElement(node, node, elem);
- }
- else {
- // Only handle HTML elements and text nodes.
- continue;
- }
- lastStyledNode = node;
-
- CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
- float currentFontSize = computedFontSize(node);
- float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
- if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
- inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
- currentFontSize = computedFontSize(node);
- }
- if (currentFontSize != desiredFontSize) {
- QString desiredFontSizeString = QString::number(desiredFontSize);
- desiredFontSizeString += "px";
- inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
- setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
- }
- if (inlineStyleDecl->length() == 0) {
- removeNodeAttribute(elem, ATTR_STYLE);
- if (isEmptyStyleSpan(elem))
- emptySpans.append(elem);
- }
- }
-
- for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
- removeNodePreservingChildren(it.current());
-}
-
-#undef NoFontDelta
-#undef MinimumFontSize
-
-void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
-{
- // adjust to the positions we want to use for applying style
- Position start(endingSelection().start().downstream().equivalentRangeCompliantPosition());
- Position end(endingSelection().end().upstream());
-
- if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
- Position swap = start;
- start = end;
- end = swap;
- }
-
- // update document layout once before removing styles
- // so that we avoid the expense of updating before each and every call
- // to check a computed style
- document()->updateLayout();
-
- // split the start node and containing element if the selection starts inside of it
- bool splitStart = splitTextElementAtStartIfNeeded(start, end);
- if (splitStart) {
- start = endingSelection().start();
- end = endingSelection().end();
- }
-
- // split the end node and containing element if the selection ends inside of it
- bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
- start = endingSelection().start();
- end = endingSelection().end();
-
- // Remove style from the selection.
- // Use the upstream position of the start for removing style.
- // This will ensure we remove all traces of the relevant styles from the selection
- // and prevent us from adding redundant ones, as described in:
- // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
- removeInlineStyle(style, start.upstream(), end);
- start = endingSelection().start();
- end = endingSelection().end();
-
- if (splitStart) {
- bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
- if (mergedStart) {
- start = endingSelection().start();
- end = endingSelection().end();
- }
- }
-
- if (splitEnd) {
- mergeEndWithNextIfIdentical(start, end);
- start = endingSelection().start();
- end = endingSelection().end();
- }
-
- // update document layout once before running the rest of the function
- // so that we avoid the expense of updating before each and every call
- // to check a computed style
- document()->updateLayout();
-
- if (start.node() == end.node()) {
- // simple case...start and end are the same node
- addInlineStyleIfNeeded(style, start.node(), end.node());
- }
- else {
- NodeImpl *node = start.node();
- if (start.offset() >= start.node()->caretMaxOffset())
- node = node->traverseNextNode();
- while (1) {
- if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
- NodeImpl *runStart = node;
- while (1) {
- NodeImpl *next = node->traverseNextNode();
- // Break if node is the end node, or if the next node does not fit in with
- // the current group.
- if (node == end.node() ||
- runStart->parentNode() != next->parentNode() ||
- (next->isHTMLElement() && next->id() != ID_BR) ||
- (next->renderer() && !next->renderer()->isInline()))
- break;
- node = next;
- }
- // Now apply style to the run we found.
- addInlineStyleIfNeeded(style, runStart, node);
- }
- if (node == end.node())
- break;
- node = node->traverseNextNode();
- }
- }
-
- if (splitStart || splitEnd) {
- cleanUpEmptyStyleSpans(start, end);
- }
-}
-
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand: style-removal helpers
-
-bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
-{
- QValueListConstIterator<CSSProperty> end;
- for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
- switch ((*it).id()) {
- case CSS_PROP_FONT_WEIGHT:
- if (elem->id() == ID_B)
- return true;
- break;
- case CSS_PROP_FONT_STYLE:
- if (elem->id() == ID_I)
- return true;
- }
- }
-
- return false;
-}
-
-void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
-{
- // This node can be removed.
- // EDIT FIXME: This does not handle the case where the node
- // has attributes. But how often do people add attributes to <B> tags?
- // Not so often I think.
- ASSERT(elem);
- removeNodePreservingChildren(elem);
-}
-
-void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
-{
- ASSERT(style);
- ASSERT(elem);
-
- if (elem->id() != ID_FONT)
- return;
-
- int exceptionCode = 0;
- QValueListConstIterator<CSSProperty> end;
- for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
- switch ((*it).id()) {
- case CSS_PROP_COLOR:
- elem->removeAttribute(ATTR_COLOR, exceptionCode);
- ASSERT(exceptionCode == 0);
- break;
- case CSS_PROP_FONT_FAMILY:
- elem->removeAttribute(ATTR_FACE, exceptionCode);
- ASSERT(exceptionCode == 0);
- break;
- case CSS_PROP_FONT_SIZE:
- elem->removeAttribute(ATTR_SIZE, exceptionCode);
- ASSERT(exceptionCode == 0);
- break;
- }
- }
-
- if (isEmptyFontTag(elem))
- removeNodePreservingChildren(elem);
-}
-
-void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
-{
- ASSERT(style);
- ASSERT(elem);
-
- CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
- if (!decl)
- return;
-
- QValueListConstIterator<CSSProperty> end;
- for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
- int propertyID = (*it).id();
- CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
- if (value) {
- value->ref();
- removeCSSProperty(decl, propertyID);
- value->deref();
- }
- }
-
- if (isEmptyStyleSpan(elem))
- removeNodePreservingChildren(elem);
-}
-
-void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
-{
- ASSERT(start.isNotNull());
- ASSERT(end.isNotNull());
- ASSERT(start.node()->inDocument());
- ASSERT(end.node()->inDocument());
- ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
-
-}
-
-static bool hasTextDecorationProperty(NodeImpl *node)
-{
- if (!node->isElementNode())
- return false;
-
- ElementImpl *element = static_cast<ElementImpl *>(node);
- CSSComputedStyleDeclarationImpl style(element);
-
- CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
-
- if (value) {
- value->ref();
- DOMString valueText(value->cssText());
- value->deref();
- if (strcasecmp(valueText,"none") != 0)
- return true;
- }
-
- return false;
-}
-
-static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
-{
- NodeImpl *result = NULL;
-
- for (NodeImpl *n = node; n; n = n->parentNode()) {
- if (hasTextDecorationProperty(n))
- result = n;
- }
-
- return result;
-}
-
-CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
-{
- ASSERT(node);
- ASSERT(node->isElementNode());
-
- // non-html elements not handled yet
- if (!node->isHTMLElement())
- return 0;
-
- HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
- CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
- if (!style)
- return 0;
-
- style->ref();
- int properties[1] = { CSS_PROP_TEXT_DECORATION };
- CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
-
- CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
- if (property && strcasecmp(property->cssText(), "none") != 0) {
- removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
- }
-
- style->deref();
-
- return textDecorationStyle;
-}
-
-CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
-{
- ASSERT(node);
- ASSERT(node->isElementNode());
-
- // non-html elements not handled yet
- if (!node->isHTMLElement())
- return 0;
-
- HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
- CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
- ASSERT(computedStyle);
-
- computedStyle->ref();
-
- int properties[1] = { CSS_PROP_TEXT_DECORATION };
- CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
-
-
- CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
- if (property && strcasecmp(property->cssText(), "none") != 0) {
- property->ref();
- CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
-
- newStyle->ref();
- newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
- applyTextDecorationStyle(node, newStyle);
- newStyle->deref();
-
- property->deref();
- }
-
- computedStyle->deref();
-
- return textDecorationStyle;
-}
-
-void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
-{
- ASSERT(node);
-
- if (!style || !style->cssText().length())
- return;
-
- if (node->isTextNode()) {
- HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
- insertNodeBefore(styleSpan, node);
- surroundNodeRangeWithElement(node, node, styleSpan);
- node = styleSpan;
- }
-
- if (!node->isElementNode())
- return;
-
- HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
-
- StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
- if (styleChange.cssStyle().length() > 0) {
- DOMString cssText = styleChange.cssStyle();
- CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
- if (decl)
- cssText += decl->cssText();
- setNodeAttribute(element, ATTR_STYLE, cssText);
- }
-}
-
-void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
-{
- NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
-
- if (highestAncestor) {
- NodeImpl *nextCurrent;
- NodeImpl *nextChild;
- for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
- ASSERT(current);
-
- nextCurrent = NULL;
-
- CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
- if (decoration)
- decoration->ref();
-
- for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
- nextChild = child->nextSibling();
-
- if (node == child) {
- nextCurrent = child;
- } else if (node->isAncestor(child)) {
- applyTextDecorationStyle(child, decoration);
- nextCurrent = child;
- } else {
- applyTextDecorationStyle(child, decoration);
- }
- }
-
- if (decoration)
- decoration->deref();
- }
- }
-}
-
-void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
-{
- // We need to work in two passes. First we push down any inline
- // styles that set text decoration. Then we look for any remaining
- // styles (caused by stylesheets) and explicitly negate text
- // decoration while pushing down.
-
- pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
- document()->updateLayout();
- pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
-
- pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
- document()->updateLayout();
- pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
-}
-
-void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
-{
- ASSERT(start.isNotNull());
- ASSERT(end.isNotNull());
- ASSERT(start.node()->inDocument());
- ASSERT(end.node()->inDocument());
- ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
-
- CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
-
- if (textDecorationSpecialProperty) {
- pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
- style = style->copy();
- style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
- }
-
- // The s and e variables store the positions used to set the ending selection after style removal
- // takes place. This will help callers to recognize when either the start node or the end node
- // are removed from the document during the work of this function.
- Position s = start;
- Position e = end;
-
- NodeImpl *node = start.node();
- while (node) {
- NodeImpl *next = node->traverseNextNode();
- if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
- HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
- NodeImpl *prev = elem->traversePreviousNodePostOrder();
- NodeImpl *next = elem->traverseNextNode();
- if (isHTMLStyleNode(style, elem)) {
- removeHTMLStyleNode(elem);
- }
- else {
- removeHTMLFontStyle(style, elem);
- removeCSSStyle(style, elem);
- }
- if (!elem->inDocument()) {
- if (s.node() == elem) {
- // Since elem must have been fully selected, and it is at the start
- // of the selection, it is clear we can set the new s offset to 0.
- ASSERT(s.offset() <= s.node()->caretMinOffset());
- s = Position(next, 0);
- }
- if (e.node() == elem) {
- // Since elem must have been fully selected, and it is at the end
- // of the selection, it is clear we can set the new e offset to
- // the max range offset of prev.
- ASSERT(e.offset() >= maxRangeOffset(e.node()));
- e = Position(prev, maxRangeOffset(prev));
- }
- }
- }
- if (node == end.node())
- break;
- node = next;
- }
-
-
- if (textDecorationSpecialProperty) {
- style->deref();
- }
-
- ASSERT(s.node()->inDocument());
- ASSERT(e.node()->inDocument());
- setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
-}
-
-bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const