+2005-05-11 Maciej Stachowiak <mjs@apple.com>
+
+ Reviewed by Kevin.
+
+ - split some more individual classes out of htmlediting.cpp
+ (CompositeEditcommand, AppendnodeCommand, ApplyStyleCommand)
+
+ Also moves StyleChange directly into ApplyStyleCommand
+ implementation file, it doesn't need to be in a header at all.
+
+ * WebCore.pbproj/project.pbxproj:
+ * khtml/editing/append_node_command.cpp: Added.
+ * khtml/editing/append_node_command.h: Added.
+ * khtml/editing/apply_style_command.cpp: Added.
+ * khtml/editing/apply_style_command.h: Added.
+ * khtml/editing/composite_edit_command.cpp: Added.
+ * khtml/editing/composite_edit_command.h: Added.
+ * khtml/editing/edit_command.cpp: Minor clean-ups.
+ * khtml/editing/edit_command.h:
+ * khtml/editing/htmlediting.cpp:
+ * khtml/editing/htmlediting.h:
+
2005-05-11 Adele Peterson <adele@apple.com>
Reviewed by Darin.
refType = 4;
sourceTree = "<group>";
};
+ 65AC799F0831ED6D009385CE = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = append_node_command.cpp;
+ path = editing/append_node_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65AC79A00831ED6D009385CE = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = append_node_command.h;
+ path = editing/append_node_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65AC79A10831ED6D009385CE = {
+ fileRef = 65AC799F0831ED6D009385CE;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65AC79A20831ED6D009385CE = {
+ fileRef = 65AC79A00831ED6D009385CE;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65AC79A70831F006009385CE = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = apply_style_command.cpp;
+ path = editing/apply_style_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65AC79A80831F006009385CE = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = apply_style_command.h;
+ path = editing/apply_style_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65AC79A90831F006009385CE = {
+ fileRef = 65AC79A70831F006009385CE;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65AC79AA0831F006009385CE = {
+ fileRef = 65AC79A80831F006009385CE;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65DC16C20831DD6F0022744E = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = composite_edit_command.cpp;
+ path = editing/composite_edit_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65DC16C30831DD6F0022744E = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = composite_edit_command.h;
+ path = editing/composite_edit_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65DC16C40831DD6F0022744E = {
+ fileRef = 65DC16C20831DD6F0022744E;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65DC16C50831DD6F0022744E = {
+ fileRef = 65DC16C30831DD6F0022744E;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
65F80697054D9F86008BF776 = {
fileEncoding = 30;
isa = PBXFileReference;
93F199FB08245E59001E9ABC,
93F199FC08245E59001E9ABC,
654D87B80831973B0082DCA1,
+ 65DC16C50831DD6F0022744E,
+ 65AC79A20831ED6D009385CE,
+ 65AC79AA0831F006009385CE,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
93F19B1108245E59001E9ABC,
93276B4F0826F80F002E46CE,
654D87B70831973B0082DCA1,
+ 65DC16C40831DD6F0022744E,
+ 65AC79A10831ED6D009385CE,
+ 65AC79A90831F006009385CE,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
};
BEB1DD0805C197DF00DD1F43 = {
children = (
+ 65AC79A00831ED6D009385CE,
+ 65AC799F0831ED6D009385CE,
+ 65AC79A80831F006009385CE,
+ 65AC79A70831F006009385CE,
+ 65DC16C30831DD6F0022744E,
+ 65DC16C20831DD6F0022744E,
EDA4AC97076FB89100DD23EC,
- 654D87B50831973B0082DCA1,
654D87B60831973B0082DCA1,
+ 654D87B50831973B0082DCA1,
BE9185DD05EE59B80081354D,
BE9185E005EE59B80081354D,
BEA5DBDA075CEDA00098A432,
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "append_node_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+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);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __append_node_command_h__
+#define __append_node_command_h__
+
+#include "edit_command.h"
+
+namespace DOM {
+ class NodeImpl;
+}
+
+namespace khtml {
+
+class AppendNodeCommand : public EditCommand
+{
+public:
+ AppendNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
+ virtual ~AppendNodeCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::NodeImpl *appendChild() const { return m_appendChild; }
+ DOM::NodeImpl *parentNode() const { return m_parentNode; }
+
+private:
+ DOM::NodeImpl *m_appendChild;
+ DOM::NodeImpl *m_parentNode;
+};
+
+} // namespace khtml
+
+#endif // __append_node_command_h__
+
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "apply_style_command.h"
+
+#include "html_interchange.h"
+
+#include "css/css_valueimpl.h"
+#include "css/css_computedstyle.h"
+#include "css/cssparser.h"
+#include "css/cssproperties.h"
+#include "dom/dom_string.h"
+#include "html/html_elementimpl.h"
+#include "misc/htmltags.h"
+#include "misc/htmlattrs.h"
+#include "rendering/render_object.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_textimpl.h"
+#include "xml/dom2_rangeimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::CSSComputedStyleDeclarationImpl;
+using DOM::CSSMutableStyleDeclarationImpl;
+using DOM::CSSParser;
+using DOM::CSSPrimitiveValue;
+using DOM::CSSPrimitiveValueImpl;
+using DOM::CSSProperty;
+using DOM::CSSStyleDeclarationImpl;
+using DOM::CSSValue;
+using DOM::CSSValueImpl;
+using DOM::DOMString;
+using DOM::DoNotUpdateLayout;
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::HTMLElementImpl;
+using DOM::NamedAttrMapImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::RangeImpl;
+using DOM::TextImpl;
+
+namespace khtml {
+
+class StyleChange {
+public:
+ enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles };
+
+ explicit StyleChange(CSSStyleDeclarationImpl *, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
+ StyleChange(CSSStyleDeclarationImpl *, const Position &, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
+
+ static ELegacyHTMLStyles styleModeForParseMode(bool);
+
+ DOMString cssStyle() const { return m_cssStyle; }
+ bool applyBold() const { return m_applyBold; }
+ bool applyItalic() const { return m_applyItalic; }
+ bool applyFontColor() const { return m_applyFontColor.length() > 0; }
+ bool applyFontFace() const { return m_applyFontFace.length() > 0; }
+ bool applyFontSize() const { return m_applyFontSize.length() > 0; }
+
+ DOMString fontColor() { return m_applyFontColor; }
+ DOMString fontFace() { return m_applyFontFace; }
+ DOMString fontSize() { return m_applyFontSize; }
+
+ bool usesLegacyStyles() const { return m_usesLegacyStyles; }
+
+private:
+ void init(CSSStyleDeclarationImpl *, const Position &);
+ bool checkForLegacyHTMLStyleChange(const CSSProperty *);
+ static bool currentlyHasStyle(const Position &, const CSSProperty *);
+
+ DOMString m_cssStyle;
+ bool m_applyBold;
+ bool m_applyItalic;
+ DOMString m_applyFontColor;
+ DOMString m_applyFontFace;
+ DOMString m_applyFontSize;
+ bool m_usesLegacyStyles;
+};
+
+
+
+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;
+}
+
+static DOMString &styleSpanClassString()
+{
+ static DOMString styleSpanClassString = AppleStyleSpanClass;
+ return styleSpanClassString;
+}
+
+bool isStyleSpan(const NodeImpl *node)
+{
+ if (!node || !node->isHTMLElement())
+ return false;
+
+ const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
+ return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
+}
+
+static bool isEmptyStyleSpan(const NodeImpl *node)
+{
+ if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
+ return false;
+
+ const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
+ CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
+ return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
+}
+
+static bool isEmptyFontTag(const NodeImpl *node)
+{
+ if (!node || node->id() != ID_FONT)
+ return false;
+
+ const ElementImpl *elem = static_cast<const ElementImpl *>(node);
+ NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
+ return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
+}
+
+static ElementImpl *createFontElement(DocumentImpl *document)
+{
+ int exceptionCode = 0;
+ ElementImpl *fontNode = document->createHTMLElement("font", exceptionCode);
+ ASSERT(exceptionCode == 0);
+ fontNode->setAttribute(ATTR_CLASS, styleSpanClassString());
+ return fontNode;
+}
+
+ElementImpl *createStyleSpanElement(DocumentImpl *document)
+{
+ int exceptionCode = 0;
+ ElementImpl *styleElement = document->createHTMLElement("SPAN", exceptionCode);
+ ASSERT(exceptionCode == 0);
+ styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
+ return styleElement;
+}
+
+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);
+}
+
+static int maxRangeOffset(NodeImpl *n)
+{
+ if (DOM::offsetInCharacters(n->nodeType()))
+ return n->maxOffset();
+
+ if (n->isElementNode())
+ return n->childNodeCount();
+
+ return 1;
+}
+
+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
+{
+ ASSERT(node);
+ ASSERT(node->isElementNode());
+
+ Position pos = Position(node, node->childNodeCount()).upstream();
+ return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
+ RangeImpl::compareBoundaryPoints(pos, end) <= 0;
+}
+
+bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
+{
+ ASSERT(node);
+ ASSERT(node->isElementNode());
+
+ Position pos = Position(node, node->childNodeCount()).upstream();
+ bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
+ bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
+
+ return isFullyBeforeStart || isFullyAfterEnd;
+}
+
+
+//------------------------------------------------------------------------------------------
+// ApplyStyleCommand: style-application helpers
+
+bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
+{
+ if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
+ long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
+ TextImpl *text = static_cast<TextImpl *>(start.node());
+ splitTextNode(text, start.offset());
+ setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
+ return true;
+ }
+ return false;
+}
+
+bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
+{
+ if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
+ TextImpl *text = static_cast<TextImpl *>(end.node());
+ splitTextNode(text, end.offset());
+
+ NodeImpl *prevNode = text->previousSibling();
+ ASSERT(prevNode);
+ NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
+ ASSERT(startNode);
+ setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
+ return true;
+ }
+ return false;
+}
+
+bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
+{
+ if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
+ long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
+ TextImpl *text = static_cast<TextImpl *>(start.node());
+ splitTextNodeContainingElement(text, start.offset());
+
+ setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
+ return true;
+ }
+ return false;
+}
+
+bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
+{
+ if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
+ TextImpl *text = static_cast<TextImpl *>(end.node());
+ splitTextNodeContainingElement(text, end.offset());
+
+ NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
+ ASSERT(prevNode);
+ NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
+ ASSERT(startNode);
+ setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
+ return true;
+ }
+ return false;
+}
+
+static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
+{
+ // check that tag name and all attribute names and values are identical
+
+ if (!first->isElementNode())
+ return false;
+
+ if (!second->isElementNode())
+ return false;
+
+ ElementImpl *firstElement = static_cast<ElementImpl *>(first);
+ ElementImpl *secondElement = static_cast<ElementImpl *>(second);
+
+ if (firstElement->id() != secondElement->id())
+ return false;
+
+ NamedAttrMapImpl *firstMap = firstElement->attributes();
+ NamedAttrMapImpl *secondMap = secondElement->attributes();
+
+ unsigned firstLength = firstMap->length();
+
+ if (firstLength != secondMap->length())
+ return false;
+
+ for (unsigned i = 0; i < firstLength; i++) {
+ DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
+ DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
+
+ if (!secondAttribute || attribute->value() != secondAttribute->value())
+ return false;
+ }
+
+ return true;
+}
+
+bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
+{
+ NodeImpl *startNode = start.node();
+ long startOffset = start.offset();
+
+ if (start.node()->isAtomicNode()) {
+ if (start.offset() != 0)
+ return false;
+
+ if (start.node()->previousSibling())
+ return false;
+
+ startNode = start.node()->parent();
+ startOffset = 0;
+ }
+
+ if (!startNode->isElementNode())
+ return false;
+
+ if (startOffset != 0)
+ return false;
+
+ NodeImpl *previousSibling = startNode->previousSibling();
+
+ if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
+ ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
+ ElementImpl *element = static_cast<ElementImpl *>(startNode);
+ NodeImpl *startChild = element->firstChild();
+ ASSERT(startChild);
+ mergeIdenticalElements(previousElement, element);
+
+ long startOffsetAdjustment = startChild->nodeIndex();
+ long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
+
+ setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
+ Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
+
+ return true;
+ }
+
+ return false;
+}
+
+bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
+{
+ NodeImpl *endNode = end.node();
+ int endOffset = end.offset();
+
+ if (endNode->isAtomicNode()) {
+ if (endOffset < endNode->caretMaxOffset())
+ return false;
+
+ unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
+ if (end.node()->nextSibling())
+ return false;
+
+ endNode = end.node()->parent();
+ endOffset = parentLastOffset;
+ }
+
+ if (!endNode->isElementNode() || endNode->id() == ID_BR)
+ return false;
+
+ NodeImpl *nextSibling = endNode->nextSibling();
+
+ if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
+ ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
+ ElementImpl *element = static_cast<ElementImpl *>(endNode);
+ NodeImpl *nextChild = nextElement->firstChild();
+
+ mergeIdenticalElements(element, nextElement);
+
+ NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
+ ASSERT(startNode);
+
+ int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
+
+ setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
+ Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
+ return true;
+ }
+
+ return false;
+}
+
+void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
+{
+ NodeImpl *node;
+ for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
+ }
+
+ if (node && isEmptyStyleSpan(node->previousSibling())) {
+ removeNodePreservingChildren(node->previousSibling());
+ }
+
+ if (start.node() == end.node()) {
+ if (start.node()->isTextNode()) {
+ for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
+ if (isEmptyStyleSpan(cur)) {
+ removeNodePreservingChildren(cur);
+ break;
+ }
+ }
+
+ }
+ } else {
+ if (start.node()->isTextNode()) {
+ for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
+ if (isEmptyStyleSpan(cur)) {
+ removeNodePreservingChildren(cur);
+ break;
+ }
+ }
+ }
+
+ if (end.node()->isTextNode()) {
+ for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
+ if (isEmptyStyleSpan(cur)) {
+ removeNodePreservingChildren(cur);
+ break;
+ }
+ }
+ }
+ }
+
+ for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
+ }
+ if (node && isEmptyStyleSpan(node->nextSibling())) {
+ removeNodePreservingChildren(node->nextSibling());
+ }
+}
+
+void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
+{
+ ASSERT(startNode);
+ ASSERT(endNode);
+ ASSERT(element);
+
+ NodeImpl *node = startNode;
+ while (1) {
+ NodeImpl *next = node->traverseNextNode();
+ if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
+ removeNode(node);
+ appendNode(node, element);
+ }
+ if (node == endNode)
+ break;
+ node = next;
+ }
+}
+
+void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
+{
+ // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
+ // inline content.
+ if (!node)
+ return;
+
+ HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
+ if (!block)
+ return;
+
+ StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
+ if (styleChange.cssStyle().length() > 0) {
+ moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
+ block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
+ DOMString cssText = styleChange.cssStyle();
+ CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
+ if (decl)
+ cssText += decl->cssText();
+ setNodeAttribute(block, ATTR_STYLE, cssText);
+ }
+}
+
+void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
+{
+ StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
+ int exceptionCode = 0;
+
+ //
+ // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
+ //
+ if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
+ ElementImpl *fontElement = createFontElement(document());
+ ASSERT(exceptionCode == 0);
+ insertNodeBefore(fontElement, startNode);
+ if (styleChange.applyFontColor())
+ fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
+ if (styleChange.applyFontFace())
+ fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
+ if (styleChange.applyFontSize())
+ fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
+ surroundNodeRangeWithElement(startNode, endNode, fontElement);
+ }
+
+ if (styleChange.cssStyle().length() > 0) {
+ ElementImpl *styleElement = createStyleSpanElement(document());
+ styleElement->ref();
+ styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
+ insertNodeBefore(styleElement, startNode);
+ styleElement->deref();
+ surroundNodeRangeWithElement(startNode, endNode, styleElement);
+ }
+
+ if (styleChange.applyBold()) {
+ ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
+ ASSERT(exceptionCode == 0);
+ insertNodeBefore(boldElement, startNode);
+ surroundNodeRangeWithElement(startNode, endNode, boldElement);
+ }
+
+ if (styleChange.applyItalic()) {
+ ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
+ ASSERT(exceptionCode == 0);
+ insertNodeBefore(italicElement, startNode);
+ surroundNodeRangeWithElement(startNode, endNode, italicElement);
+ }
+}
+
+float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
+{
+ float size = 0.0f;
+
+ if (!node)
+ return size;
+
+ Position pos(const_cast<NodeImpl *>(node), 0);
+ CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
+ if (!computedStyle)
+ return size;
+ computedStyle->ref();
+
+ CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
+ if (value) {
+ value->ref();
+ size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
+ value->deref();
+ }
+
+ computedStyle->deref();
+ return size;
+}
+
+void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
+{
+ if (!node)
+ return;
+
+ Position newStart = start;
+ Position newEnd = end;
+
+ NodeImpl *child = node->firstChild();
+ while (child) {
+ NodeImpl *next = child->nextSibling();
+ if (child->isTextNode() && next && next->isTextNode()) {
+ TextImpl *childText = static_cast<TextImpl *>(child);
+ TextImpl *nextText = static_cast<TextImpl *>(next);
+ if (next == start.node())
+ newStart = Position(childText, childText->length() + start.offset());
+ if (next == end.node())
+ newEnd = Position(childText, childText->length() + end.offset());
+ DOMString textToMove = nextText->data();
+ insertTextIntoNode(childText, childText->length(), textToMove);
+ removeNode(next);
+ // don't move child node pointer. it may want to merge with more text nodes.
+ }
+ else {
+ child = child->nextSibling();
+ }
+ }
+
+ setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __apply_style_command_h__
+#define __apply_style_command_h__
+
+#include "composite_edit_command.h"
+
+namespace DOM {
+ class HTMLElementImpl;
+}
+
+namespace khtml {
+
+class ApplyStyleCommand : public CompositeEditCommand
+{
+public:
+ enum EPropertyLevel { PropertyDefault, ForceBlockProperties };
+
+ ApplyStyleCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *style, EditAction editingAction=EditActionChangeAttributes, EPropertyLevel=PropertyDefault);
+ virtual ~ApplyStyleCommand();
+
+ virtual void doApply();
+ virtual EditAction editingAction() const;
+
+ DOM::CSSMutableStyleDeclarationImpl *style() const { return m_style; }
+
+private:
+ // style-removal helpers
+ bool isHTMLStyleNode(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
+ void removeHTMLStyleNode(DOM::HTMLElementImpl *);
+ void removeHTMLFontStyle(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
+ void removeCSSStyle(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
+ void removeBlockStyle(DOM::CSSMutableStyleDeclarationImpl *, const DOM::Position &start, const DOM::Position &end);
+ void removeInlineStyle(DOM::CSSMutableStyleDeclarationImpl *, const DOM::Position &start, const DOM::Position &end);
+ bool nodeFullySelected(DOM::NodeImpl *, const DOM::Position &start, const DOM::Position &end) const;
+ bool nodeFullyUnselected(DOM::NodeImpl *node, const DOM::Position &start, const DOM::Position &end) const;
+ DOM::CSSMutableStyleDeclarationImpl *extractTextDecorationStyle(DOM::NodeImpl *node);
+ DOM::CSSMutableStyleDeclarationImpl *extractAndNegateTextDecorationStyle(DOM::NodeImpl *node);
+ void applyTextDecorationStyle(DOM::NodeImpl *node, DOM::CSSMutableStyleDeclarationImpl *style);
+ void pushDownTextDecorationStyleAroundNode(DOM::NodeImpl *node, const DOM::Position &start, const DOM::Position &end, bool force);
+ void pushDownTextDecorationStyleAtBoundaries(const DOM::Position &start, const DOM::Position &end);
+
+ // style-application helpers
+ void applyBlockStyle(DOM::CSSMutableStyleDeclarationImpl *);
+ void applyRelativeFontStyleChange(DOM::CSSMutableStyleDeclarationImpl *);
+ void applyInlineStyle(DOM::CSSMutableStyleDeclarationImpl *);
+ void addBlockStyleIfNeeded(DOM::CSSMutableStyleDeclarationImpl *, DOM::NodeImpl *);
+ void addInlineStyleIfNeeded(DOM::CSSMutableStyleDeclarationImpl *, DOM::NodeImpl *start, DOM::NodeImpl *end);
+ bool splitTextAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
+ bool splitTextAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
+ bool splitTextElementAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
+ bool splitTextElementAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
+ bool mergeStartWithPreviousIfIdentical(const DOM::Position &start, const DOM::Position &end);
+ bool mergeEndWithNextIfIdentical(const DOM::Position &start, const DOM::Position &end);
+ void cleanUpEmptyStyleSpans(const DOM::Position &start, const DOM::Position &end);
+
+ void surroundNodeRangeWithElement(DOM::NodeImpl *start, DOM::NodeImpl *end, DOM::ElementImpl *element);
+ float computedFontSize(const DOM::NodeImpl *);
+ void joinChildTextNodes(DOM::NodeImpl *, const DOM::Position &start, const DOM::Position &end);
+
+ DOM::CSSMutableStyleDeclarationImpl *m_style;
+ EditAction m_editingAction;
+ EPropertyLevel m_propertyLevel;
+};
+
+bool isStyleSpan(const DOM::NodeImpl *node);
+DOM::ElementImpl *createStyleSpanElement(DOM::DocumentImpl *document);
+
+} // namespace khtml
+
+#endif // __apply_style_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "composite_edit_command.h"
+
+#include "append_node_command.h"
+#include "htmlediting.h"
+#include "visible_units.h"
+
+#include "misc/htmlattrs.h"
+#include "misc/htmltags.h"
+#include "rendering/render_text.h"
+#include "xml/dom2_rangeimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::CSSStyleDeclarationImpl;
+using DOM::DocumentImpl;
+using DOM::DOMString;
+using DOM::DOMStringImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::RangeImpl;
+using DOM::TextImpl;
+
+namespace khtml {
+
+static const DOMString &blockPlaceholderClassString();
+
+//------------------------------------------------------------------------------------------
+// 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;
+ }
+}
+
+ElementImpl *createBlockPlaceholderElement(DocumentImpl *document)
+{
+ int exceptionCode = 0;
+ ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode);
+ ASSERT(exceptionCode == 0);
+ breakNode->setAttribute(ATTR_CLASS, blockPlaceholderClassString());
+ return breakNode;
+}
+
+static const DOMString &blockPlaceholderClassString()
+{
+ static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
+ return blockPlaceholderClassString;
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __composite_edit_command_h__
+#define __composite_edit_command_h__
+
+#include "edit_command.h"
+#include "qvaluelist.h"
+
+namespace DOM {
+ class CSSStyleDeclarationImpl;
+ class DOMString;
+ class TextImpl;
+}
+
+namespace khtml {
+
+class CompositeEditCommand : public EditCommand
+{
+public:
+ CompositeEditCommand(DOM::DocumentImpl *);
+
+ virtual void doUnapply();
+ virtual void doReapply();
+
+protected:
+ //
+ // sugary-sweet convenience functions to help create and apply edit commands in composite commands
+ //
+ void appendNode(DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
+ void applyCommandToComposite(EditCommandPtr &);
+ void applyStyle(DOM::CSSStyleDeclarationImpl *style, EditAction editingAction=EditActionChangeAttributes);
+ void deleteKeyPressed();
+ void deleteSelection(bool smartDelete=false, bool mergeBlocksAfterDelete=true);
+ void deleteSelection(const Selection &selection, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
+ void deleteTextFromNode(DOM::TextImpl *node, long offset, long count);
+ void inputText(const DOM::DOMString &text, bool selectInsertedText = false);
+ void insertNodeAfter(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+ void insertNodeAt(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild, long offset);
+ void insertNodeBefore(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+ void insertParagraphSeparator();
+ void insertTextIntoNode(DOM::TextImpl *node, long offset, const DOM::DOMString &text);
+ void joinTextNodes(DOM::TextImpl *text1, DOM::TextImpl *text2);
+ void rebalanceWhitespace();
+ void removeCSSProperty(DOM::CSSStyleDeclarationImpl *, int property);
+ void removeFullySelectedNode(DOM::NodeImpl *node);
+ void removeNodeAttribute(DOM::ElementImpl *, int attribute);
+ void removeChildrenInRange(DOM::NodeImpl *node, int from, int to);
+ void removeNode(DOM::NodeImpl *removeChild);
+ void removeNodePreservingChildren(DOM::NodeImpl *node);
+ void replaceTextInNode(DOM::TextImpl *node, long offset, long count, const DOM::DOMString &replacementText);
+ void setNodeAttribute(DOM::ElementImpl *, int attribute, const DOM::DOMString &);
+ void splitTextNode(DOM::TextImpl *text, long offset);
+ void splitElement(DOM::ElementImpl *element, DOM::NodeImpl *atChild);
+ void mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second);
+ void wrapContentsInDummySpan(DOM::ElementImpl *element);
+ void splitTextNodeContainingElement(DOM::TextImpl *text, long offset);
+
+ void deleteInsignificantText(DOM::TextImpl *, int start, int end);
+ void deleteInsignificantText(const DOM::Position &start, const DOM::Position &end);
+ void deleteInsignificantTextDownstream(const DOM::Position &);
+
+ DOM::NodeImpl *appendBlockPlaceholder(DOM::NodeImpl *);
+ DOM::NodeImpl *insertBlockPlaceholder(const DOM::Position &pos);
+ DOM::NodeImpl *addBlockPlaceholderIfNeeded(DOM::NodeImpl *);
+ bool removeBlockPlaceholder(DOM::NodeImpl *);
+ DOM::NodeImpl *findBlockPlaceholder(DOM::NodeImpl *);
+
+ void moveParagraphContentsToNewBlockIfNecessary(const DOM::Position &);
+
+ QValueList<EditCommandPtr> m_cmds;
+};
+
+} // namespace khtml
+
+#endif // __composite_edit_command_h__
*/
#include "edit_command.h"
+#include "selection.h"
+#include "khtml_part.h"
#include "xml/dom_position.h"
#include "xml/dom_docimpl.h"
#if APPLE_CHANGES
#include "KWQAssertions.h"
-#include "KWQLogging.h"
-#include "KWQKHTMLPart.h"
#else
-#define ASSERT(assertion) ((void)0)
-#define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
-#define ASSERT_NOT_REACHED() ((void)0)
-#define LOG(channel, formatAndArgs...) ((void)0)
-#define ERROR(formatAndArgs...) ((void)0)
#define ASSERT(assertion) assert(assertion)
-#if LOG_DISABLED
-#define debugPosition(a,b) ((void)0)
-#define debugNode(a,b) ((void)0)
-#endif
#endif
using DOM::DocumentImpl;
namespace khtml {
-class EditCommand;
-//------------------------------------------------------------------------------------------
-// EditCommandPtr
-
-class EditCommandPtr : public SharedPtr<EditCommand>
-{
-public:
- EditCommandPtr();
- EditCommandPtr(EditCommand *);
- EditCommandPtr(const EditCommandPtr &);
- ~EditCommandPtr();
-
- EditCommandPtr &operator=(const EditCommandPtr &);
-
- bool isCompositeStep() const;
-
- void apply() const;
- void unapply() const;
- void reapply() const;
-
- EditAction editingAction() const;
-
- DOM::DocumentImpl * const document() const;
-
- Selection startingSelection() const;
- Selection endingSelection() const;
-
- void setStartingSelection(const Selection &s) const;
- void setStartingSelection(const VisiblePosition &p) const;
- void setStartingSelection(const DOM::Position &p, EAffinity affinity) const;
- void setEndingSelection(const Selection &s) const;
- void setEndingSelection(const VisiblePosition &p) const;
- void setEndingSelection(const DOM::Position &p, EAffinity affinity) const;
-
- DOM::CSSMutableStyleDeclarationImpl *typingStyle() const;
- void setTypingStyle(DOM::CSSMutableStyleDeclarationImpl *) const;
-
- EditCommandPtr parent() const;
- void setParent(const EditCommandPtr &) const;
-
- bool isInsertTextCommand() const;
- bool isInsertLineBreakCommand() const;
- bool isTypingCommand() const;
-
- static EditCommandPtr &emptyCommand();
-};
-
//------------------------------------------------------------------------------------------
// EditCommand
EditCommand *m_parent;
};
+class EditCommandPtr : public SharedPtr<EditCommand>
+{
+public:
+ EditCommandPtr();
+ EditCommandPtr(EditCommand *);
+ EditCommandPtr(const EditCommandPtr &);
+ ~EditCommandPtr();
+
+ EditCommandPtr &operator=(const EditCommandPtr &);
+
+ bool isCompositeStep() const;
+
+ void apply() const;
+ void unapply() const;
+ void reapply() const;
+
+ EditAction editingAction() const;
+
+ DOM::DocumentImpl * const document() const;
+
+ Selection startingSelection() const;
+ Selection endingSelection() const;
+
+ void setStartingSelection(const Selection &s) const;
+ void setStartingSelection(const VisiblePosition &p) const;
+ void setStartingSelection(const DOM::Position &p, EAffinity affinity) const;
+ void setEndingSelection(const Selection &s) const;
+ void setEndingSelection(const VisiblePosition &p) const;
+ void setEndingSelection(const DOM::Position &p, EAffinity affinity) const;
+
+ DOM::CSSMutableStyleDeclarationImpl *typingStyle() const;
+ void setTypingStyle(DOM::CSSMutableStyleDeclarationImpl *) const;
+
+ EditCommandPtr parent() const;
+ void setParent(const EditCommandPtr &) const;
+
+ bool isInsertTextCommand() const;
+ bool isInsertLineBreakCommand() const;
+ bool isTypingCommand() const;
+
+ static EditCommandPtr &emptyCommand();
+};
+
} // namespace khtml
#endif // __edit_command_h__
static const int spacesPerTab = 4;
-static bool isTableStructureNode(const NodeImpl *node)
+bool isTableStructureNode(const NodeImpl *node)
{
RenderObject *r = node->renderer();
return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
return nonBreakingSpaceString;
}
-static DOMString &styleSpanClassString()
-{
- static DOMString styleSpanClassString = AppleStyleSpanClass;
- return styleSpanClassString;
-}
-
-static bool isEmptyStyleSpan(const NodeImpl *node)
-{
- if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
- return false;
-
- const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
- CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
- return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
-}
-
-static bool isStyleSpan(const NodeImpl *node)
-{
- if (!node || !node->isHTMLElement())
- return false;
-
- const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
- return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
-}
-
-static bool isEmptyFontTag(const NodeImpl *node)
-{
- if (!node || node->id() != ID_FONT)
- return false;
-
- const ElementImpl *elem = static_cast<const ElementImpl *>(node);
- NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
- return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
-}
-
-static DOMString &blockPlaceholderClassString()
-{
- static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
- return blockPlaceholderClassString;
-}
-
static DOMString &matchNearestBlockquoteColorString()
{
static DOMString matchNearestBlockquoteColorString = "match";
it.current()->deref();
}
-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
+static int maxRangeOffset(NodeImpl *n)
{
- ASSERT(node);
- ASSERT(node->isElementNode());
+ if (DOM::offsetInCharacters(n->nodeType()))
+ return n->maxOffset();
+
+ if (n->isElementNode())
+ return n->childNodeCount();
- Position pos = Position(node, node->childNodeCount()).upstream();
- return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
- RangeImpl::compareBoundaryPoints(pos, end) <= 0;
+ return 1;
}
-bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
+static int maxDeepOffset(NodeImpl *n)
{
- ASSERT(node);
- ASSERT(node->isElementNode());
+ if (n->isAtomicNode())
+ return n->caretMaxOffset();
- Position pos = Position(node, node->childNodeCount()).upstream();
- bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
- bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
+ if (n->isElementNode())
+ return n->childNodeCount();
- return isFullyBeforeStart || isFullyAfterEnd;
+ return 1;
}
-
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand: style-application helpers
-
-bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
+static void debugPosition(const char *prefix, const Position &pos)
{
- if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
- long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
- TextImpl *text = static_cast<TextImpl *>(start.node());
- splitTextNode(text, start.offset());
- setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
- return true;
- }
- return false;
+ 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());
}
-bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
+static void debugNode(const char *prefix, const NodeImpl *node)
{
- if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
- TextImpl *text = static_cast<TextImpl *>(end.node());
- splitTextNode(text, end.offset());
-
- NodeImpl *prevNode = text->previousSibling();
- ASSERT(prevNode);
- NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
- ASSERT(startNode);
- setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
- return true;
- }
- return false;
+ if (!prefix)
+ prefix = "";
+ if (!node)
+ LOG(Editing, "%s <null>", prefix);
+ else
+ LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
}
-bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
+static bool isSpecialElement(NodeImpl *n)
{
- if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
- long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
- TextImpl *text = static_cast<TextImpl *>(start.node());
- splitTextNodeContainingElement(text, start.offset());
+ if (!n->isHTMLElement())
+ return false;
- setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
+ if (n->id() == ID_A && n->isLink())
return true;
- }
- return false;
-}
-bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
-{
- if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
- TextImpl *text = static_cast<TextImpl *>(end.node());
- splitTextNodeContainingElement(text, end.offset());
-
- NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
- ASSERT(prevNode);
- NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
- ASSERT(startNode);
- setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
+ if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
return true;
- }
- return false;
-}
-
-static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
-{
- // check that tag name and all attribute names and values are identical
- if (!first->isElementNode())
- return false;
-
- if (!second->isElementNode())
- return false;
+ RenderObject *renderer = n->renderer();
- ElementImpl *firstElement = static_cast<ElementImpl *>(first);
- ElementImpl *secondElement = static_cast<ElementImpl *>(second);
-
- if (firstElement->id() != secondElement->id())
- return false;
+ if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
+ return true;
- NamedAttrMapImpl *firstMap = firstElement->attributes();
- NamedAttrMapImpl *secondMap = secondElement->attributes();
+ if (renderer && renderer->style()->isFloating())
+ return true;
- unsigned firstLength = firstMap->length();
+ if (renderer && renderer->style()->position() != STATIC)
+ return true;
- if (firstLength != secondMap->length())
- return false;
+ return false;
+}
- for (unsigned i = 0; i < firstLength; i++) {
- DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
- DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
+// 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);
- if (!secondAttribute || attribute->value() != secondAttribute->value())
+ for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
+ if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
return false;
+ if (isSpecialElement(n))
+ return true;
}
-
- return true;
+
+ return false;
}
-bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
+static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
{
- NodeImpl *startNode = start.node();
- long startOffset = start.offset();
+ VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
- if (start.node()->isAtomicNode()) {
- if (start.offset() != 0)
+ for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
+ if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
return false;
-
- if (start.node()->previousSibling())
+ if (n->rootEditableElement() == NULL)
return false;
-
- startNode = start.node()->parent();
- startOffset = 0;
- }
-
- if (!startNode->isElementNode())
- return false;
-
- if (startOffset != 0)
- return false;
-
- NodeImpl *previousSibling = startNode->previousSibling();
-
- if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
- ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
- ElementImpl *element = static_cast<ElementImpl *>(startNode);
- NodeImpl *startChild = element->firstChild();
- ASSERT(startChild);
- mergeIdenticalElements(previousElement, element);
-
- long startOffsetAdjustment = startChild->nodeIndex();
- long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
-
- setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
- Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
-
- return true;
+ if (isSpecialElement(n))
+ return true;
}
return false;
}
-bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
+static Position positionBeforeNode(NodeImpl *node)
{
- NodeImpl *endNode = end.node();
- int endOffset = end.offset();
+ return Position(node->parentNode(), node->nodeIndex());
+}
- if (endNode->isAtomicNode()) {
- if (endOffset < endNode->caretMaxOffset())
- return false;
+static Position positionBeforeContainingSpecialElement(const Position& pos)
+{
+ ASSERT(isFirstVisiblePositionInSpecialElement(pos));
- unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
- if (end.node()->nextSibling())
- return false;
+ VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
+
+ NodeImpl *outermostSpecialElement = NULL;
- endNode = end.node()->parent();
- endOffset = parentLastOffset;
+ 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);
- if (!endNode->isElementNode() || endNode->id() == ID_BR)
- return false;
-
- NodeImpl *nextSibling = endNode->nextSibling();
-
- if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
- ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
- ElementImpl *element = static_cast<ElementImpl *>(endNode);
- NodeImpl *nextChild = nextElement->firstChild();
-
- mergeIdenticalElements(element, nextElement);
+ Position result = positionBeforeNode(outermostSpecialElement);
+ if (result.isNull() || !result.node()->rootEditableElement())
+ return pos;
+
+ return result;
+}
- NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
- ASSERT(startNode);
+static bool isLastVisiblePositionInSpecialElement(const Position& pos)
+{
+ // make sure to get a range-compliant version of the position
+ Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
- int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
+ VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
- setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
- Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
- return true;
+ 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;
}
-void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
+static Position positionAfterNode(NodeImpl *node)
{
- NodeImpl *node;
- for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
- }
+ return Position(node->parentNode(), node->nodeIndex() + 1);
+}
- if (node && isEmptyStyleSpan(node->previousSibling())) {
- removeNodePreservingChildren(node->previousSibling());
- }
+static Position positionAfterContainingSpecialElement(const Position& pos)
+{
+ ASSERT(isLastVisiblePositionInSpecialElement(pos));
- if (start.node() == end.node()) {
- if (start.node()->isTextNode()) {
- for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
- if (isEmptyStyleSpan(cur)) {
- removeNodePreservingChildren(cur);
- break;
- }
- }
+ // make sure to get a range-compliant version of the position
+ Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
- }
- } else {
- if (start.node()->isTextNode()) {
- for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
- if (isEmptyStyleSpan(cur)) {
- removeNodePreservingChildren(cur);
- break;
- }
- }
- }
+ VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
- if (end.node()->isTextNode()) {
- for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
- if (isEmptyStyleSpan(cur)) {
- removeNodePreservingChildren(cur);
- break;
- }
- }
- }
- }
-
- for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
- }
- if (node && isEmptyStyleSpan(node->nextSibling())) {
- removeNodePreservingChildren(node->nextSibling());
- }
-}
+ NodeImpl *outermostSpecialElement = NULL;
-void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
-{
- ASSERT(startNode);
- ASSERT(endNode);
- ASSERT(element);
-
- NodeImpl *node = startNode;
- while (1) {
- NodeImpl *next = node->traverseNextNode();
- if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
- removeNode(node);
- appendNode(node, element);
- }
- if (node == endNode)
+ for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
+ if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
break;
- node = next;
+ if (n->rootEditableElement() == NULL)
+ break;
+ if (isSpecialElement(n))
+ outermostSpecialElement = n;
}
-}
+
+ ASSERT(outermostSpecialElement);
-void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
-{
- // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
- // inline content.
- if (!node)
- return;
+ Position result = positionAfterNode(outermostSpecialElement);
+ if (result.isNull() || !result.node()->rootEditableElement())
+ return pos;
- HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
- if (!block)
- return;
-
- StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
- if (styleChange.cssStyle().length() > 0) {
- moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
- block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
- DOMString cssText = styleChange.cssStyle();
- CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
- if (decl)
- cssText += decl->cssText();
- setNodeAttribute(block, ATTR_STYLE, cssText);
- }
+ return result;
}
-void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
+static Position positionOutsideContainingSpecialElement(const Position &pos)
{
- StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
- int exceptionCode = 0;
-
- //
- // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
- //
- if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
- ElementImpl *fontElement = createFontElement(document());
- ASSERT(exceptionCode == 0);
- insertNodeBefore(fontElement, startNode);
- if (styleChange.applyFontColor())
- fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
- if (styleChange.applyFontFace())
- fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
- if (styleChange.applyFontSize())
- fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
- surroundNodeRangeWithElement(startNode, endNode, fontElement);
- }
-
- if (styleChange.cssStyle().length() > 0) {
- ElementImpl *styleElement = createStyleSpanElement(document());
- styleElement->ref();
- styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
- insertNodeBefore(styleElement, startNode);
- styleElement->deref();
- surroundNodeRangeWithElement(startNode, endNode, styleElement);
- }
-
- if (styleChange.applyBold()) {
- ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
- ASSERT(exceptionCode == 0);
- insertNodeBefore(boldElement, startNode);
- surroundNodeRangeWithElement(startNode, endNode, boldElement);
+ if (isFirstVisiblePositionInSpecialElement(pos)) {
+ return positionBeforeContainingSpecialElement(pos);
+ } else if (isLastVisiblePositionInSpecialElement(pos)) {
+ return positionAfterContainingSpecialElement(pos);
}
- if (styleChange.applyItalic()) {
- ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
- ASSERT(exceptionCode == 0);
- insertNodeBefore(italicElement, startNode);
- surroundNodeRangeWithElement(startNode, endNode, italicElement);
- }
+ return pos;
}
-float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
+static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
{
- float size = 0.0f;
-
- if (!node)
- return size;
-
- Position pos(const_cast<NodeImpl *>(node), 0);
- CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
- if (!computedStyle)
- return size;
- computedStyle->ref();
-
- CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
- if (value) {
- value->ref();
- size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
- value->deref();
- }
+ if (isFirstVisiblePositionInSpecialElement(pos)) {
+ return positionBeforeContainingSpecialElement(pos);
+ }
- computedStyle->deref();
- return size;
+ return pos;
}
-void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
+static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
{
- if (!node)
- return;
-
- Position newStart = start;
- Position newEnd = end;
-
- NodeImpl *child = node->firstChild();
- while (child) {
- NodeImpl *next = child->nextSibling();
- if (child->isTextNode() && next && next->isTextNode()) {
- TextImpl *childText = static_cast<TextImpl *>(child);
- TextImpl *nextText = static_cast<TextImpl *>(next);
- if (next == start.node())
- newStart = Position(childText, childText->length() + start.offset());
- if (next == end.node())
- newEnd = Position(childText, childText->length() + end.offset());
- DOMString textToMove = nextText->data();
- insertTextIntoNode(childText, childText->length(), textToMove);
- removeNode(next);
- // don't move child node pointer. it may want to merge with more text nodes.
- }
- else {
- child = child->nextSibling();
- }
+ if (isLastVisiblePositionInSpecialElement(pos)) {
+ return positionAfterContainingSpecialElement(pos);
}
- setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
+ return pos;
}
//------------------------------------------------------------------------------------------
return true;
}
-ElementImpl *floatRefdElement(ElementImpl *element)
-{
- assert(!element->parentNode());
- element->setParent(element->getDocument());
- element->deref();
- element->setParent(0);
- return element;
-}
-
ElementImpl *createDefaultParagraphElement(DocumentImpl *document)
{
// We would need this margin-zeroing code back if we ever return to using <p> elements for default paragraphs.
return element;
}
-ElementImpl *createBlockPlaceholderElement(DocumentImpl *document)
-{
- int exceptionCode = 0;
- ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode);
- ASSERT(exceptionCode == 0);
- breakNode->ref();
- breakNode->setAttribute(ATTR_CLASS, blockPlaceholderClassString());
- return floatRefdElement(breakNode);
-}
-
ElementImpl *createBreakElement(DocumentImpl *document)
{
int exceptionCode = 0;
return breakNode;
}
-ElementImpl *createFontElement(DocumentImpl *document)
-{
- int exceptionCode = 0;
- ElementImpl *fontNode = document->createHTMLElement("font", exceptionCode);
- ASSERT(exceptionCode == 0);
- fontNode->ref();
- fontNode->setAttribute(ATTR_CLASS, styleSpanClassString());
- return floatRefdElement(fontNode);
-}
-
-ElementImpl *createStyleSpanElement(DocumentImpl *document)
-{
- int exceptionCode = 0;
- ElementImpl *styleElement = document->createHTMLElement("SPAN", exceptionCode);
- ASSERT(exceptionCode == 0);
- styleElement->ref();
- styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
- return floatRefdElement(styleElement);
-}
-
bool isNodeRendered(const NodeImpl *node)
{
if (!node)
#define __htmlediting_h__
#include "edit_command.h"
+#include "composite_edit_command.h"
+#include "apply_style_command.h"
+
#include "dom_nodeimpl.h"
#include "editing/edit_actions.h"
#include "qmap.h"
namespace khtml {
-class EditCommand;
class Selection;
class VisiblePosition;
-//------------------------------------------------------------------------------------------
-// StyleChange
-
-class StyleChange {
-public:
- enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles };
-
- explicit StyleChange(DOM::CSSStyleDeclarationImpl *, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
- StyleChange(DOM::CSSStyleDeclarationImpl *, const DOM::Position &, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
-
- static ELegacyHTMLStyles styleModeForParseMode(bool);
-
- DOM::DOMString cssStyle() const { return m_cssStyle; }
- bool applyBold() const { return m_applyBold; }
- bool applyItalic() const { return m_applyItalic; }
- bool applyFontColor() const { return m_applyFontColor.length() > 0; }
- bool applyFontFace() const { return m_applyFontFace.length() > 0; }
- bool applyFontSize() const { return m_applyFontSize.length() > 0; }
-
- DOM::DOMString fontColor() { return m_applyFontColor; }
- DOM::DOMString fontFace() { return m_applyFontFace; }
- DOM::DOMString fontSize() { return m_applyFontSize; }
-
- bool usesLegacyStyles() const { return m_usesLegacyStyles; }
-
-private:
- void init(DOM::CSSStyleDeclarationImpl *, const DOM::Position &);
- bool checkForLegacyHTMLStyleChange(const DOM::CSSProperty *);
- static bool currentlyHasStyle(const DOM::Position &, const DOM::CSSProperty *);
-
- DOM::DOMString m_cssStyle;
- bool m_applyBold;
- bool m_applyItalic;
- DOM::DOMString m_applyFontColor;
- DOM::DOMString m_applyFontFace;
- DOM::DOMString m_applyFontSize;
- bool m_usesLegacyStyles;
-};
-
-//------------------------------------------------------------------------------------------
-// CompositeEditCommand
-
-class CompositeEditCommand : public EditCommand
-{
-public:
- CompositeEditCommand(DOM::DocumentImpl *);
-
- virtual void doUnapply();
- virtual void doReapply();
-
-protected:
- //
- // sugary-sweet convenience functions to help create and apply edit commands in composite commands
- //
- void appendNode(DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
- void applyCommandToComposite(EditCommandPtr &);
- void applyStyle(DOM::CSSStyleDeclarationImpl *style, EditAction editingAction=EditActionChangeAttributes);
- void deleteKeyPressed();
- void deleteSelection(bool smartDelete=false, bool mergeBlocksAfterDelete=true);
- void deleteSelection(const Selection &selection, bool smartDelete=false, bool mergeBlocksAfterDelete=true);
- void deleteTextFromNode(DOM::TextImpl *node, long offset, long count);
- void inputText(const DOM::DOMString &text, bool selectInsertedText = false);
- void insertNodeAfter(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
- void insertNodeAt(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild, long offset);
- void insertNodeBefore(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
- void insertParagraphSeparator();
- void insertTextIntoNode(DOM::TextImpl *node, long offset, const DOM::DOMString &text);
- void joinTextNodes(DOM::TextImpl *text1, DOM::TextImpl *text2);
- void rebalanceWhitespace();
- void removeCSSProperty(DOM::CSSStyleDeclarationImpl *, int property);
- void removeFullySelectedNode(DOM::NodeImpl *node);
- void removeNodeAttribute(DOM::ElementImpl *, int attribute);
- void removeChildrenInRange(DOM::NodeImpl *node, int from, int to);
- void removeNode(DOM::NodeImpl *removeChild);
- void removeNodePreservingChildren(DOM::NodeImpl *node);
- void replaceTextInNode(DOM::TextImpl *node, long offset, long count, const DOM::DOMString &replacementText);
- void setNodeAttribute(DOM::ElementImpl *, int attribute, const DOM::DOMString &);
- void splitTextNode(DOM::TextImpl *text, long offset);
- void splitElement(DOM::ElementImpl *element, DOM::NodeImpl *atChild);
- void mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second);
- void wrapContentsInDummySpan(DOM::ElementImpl *element);
- void splitTextNodeContainingElement(DOM::TextImpl *text, long offset);
-
- void deleteInsignificantText(DOM::TextImpl *, int start, int end);
- void deleteInsignificantText(const DOM::Position &start, const DOM::Position &end);
- void deleteInsignificantTextDownstream(const DOM::Position &);
-
- DOM::NodeImpl *appendBlockPlaceholder(DOM::NodeImpl *);
- DOM::NodeImpl *insertBlockPlaceholder(const DOM::Position &pos);
- DOM::NodeImpl *addBlockPlaceholderIfNeeded(DOM::NodeImpl *);
- bool removeBlockPlaceholder(DOM::NodeImpl *);
- DOM::NodeImpl *findBlockPlaceholder(DOM::NodeImpl *);
-
- void moveParagraphContentsToNewBlockIfNecessary(const DOM::Position &);
-
- QValueList<EditCommandPtr> m_cmds;
-};
-
-//==========================================================================================
-// Concrete commands
-//------------------------------------------------------------------------------------------
-// AppendNodeCommand
-
-class AppendNodeCommand : public EditCommand
-{
-public:
- AppendNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
- virtual ~AppendNodeCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::NodeImpl *appendChild() const { return m_appendChild; }
- DOM::NodeImpl *parentNode() const { return m_parentNode; }
-
-private:
- DOM::NodeImpl *m_appendChild;
- DOM::NodeImpl *m_parentNode;
-};
-
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand
-
-class ApplyStyleCommand : public CompositeEditCommand
-{
-public:
- enum EPropertyLevel { PropertyDefault, ForceBlockProperties };
-
- ApplyStyleCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *style, EditAction editingAction=EditActionChangeAttributes, EPropertyLevel=PropertyDefault);
- virtual ~ApplyStyleCommand();
-
- virtual void doApply();
- virtual EditAction editingAction() const;
-
- DOM::CSSMutableStyleDeclarationImpl *style() const { return m_style; }
-
-private:
- // style-removal helpers
- bool isHTMLStyleNode(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
- void removeHTMLStyleNode(DOM::HTMLElementImpl *);
- void removeHTMLFontStyle(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
- void removeCSSStyle(DOM::CSSMutableStyleDeclarationImpl *, DOM::HTMLElementImpl *);
- void removeBlockStyle(DOM::CSSMutableStyleDeclarationImpl *, const DOM::Position &start, const DOM::Position &end);
- void removeInlineStyle(DOM::CSSMutableStyleDeclarationImpl *, const DOM::Position &start, const DOM::Position &end);
- bool nodeFullySelected(DOM::NodeImpl *, const DOM::Position &start, const DOM::Position &end) const;
- bool nodeFullyUnselected(DOM::NodeImpl *node, const DOM::Position &start, const DOM::Position &end) const;
- DOM::CSSMutableStyleDeclarationImpl *extractTextDecorationStyle(DOM::NodeImpl *node);
- DOM::CSSMutableStyleDeclarationImpl *extractAndNegateTextDecorationStyle(DOM::NodeImpl *node);
- void applyTextDecorationStyle(DOM::NodeImpl *node, DOM::CSSMutableStyleDeclarationImpl *style);
- void pushDownTextDecorationStyleAroundNode(DOM::NodeImpl *node, const DOM::Position &start, const DOM::Position &end, bool force);
- void pushDownTextDecorationStyleAtBoundaries(const DOM::Position &start, const DOM::Position &end);
-
- // style-application helpers
- void applyBlockStyle(DOM::CSSMutableStyleDeclarationImpl *);
- void applyRelativeFontStyleChange(DOM::CSSMutableStyleDeclarationImpl *);
- void applyInlineStyle(DOM::CSSMutableStyleDeclarationImpl *);
- void addBlockStyleIfNeeded(DOM::CSSMutableStyleDeclarationImpl *, DOM::NodeImpl *);
- void addInlineStyleIfNeeded(DOM::CSSMutableStyleDeclarationImpl *, DOM::NodeImpl *start, DOM::NodeImpl *end);
- bool splitTextAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
- bool splitTextAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
- bool splitTextElementAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
- bool splitTextElementAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
- bool mergeStartWithPreviousIfIdentical(const DOM::Position &start, const DOM::Position &end);
- bool mergeEndWithNextIfIdentical(const DOM::Position &start, const DOM::Position &end);
- void cleanUpEmptyStyleSpans(const DOM::Position &start, const DOM::Position &end);
-
- void surroundNodeRangeWithElement(DOM::NodeImpl *start, DOM::NodeImpl *end, DOM::ElementImpl *element);
- float computedFontSize(const DOM::NodeImpl *);
- void joinChildTextNodes(DOM::NodeImpl *, const DOM::Position &start, const DOM::Position &end);
-
- DOM::CSSMutableStyleDeclarationImpl *m_style;
- EditAction m_editingAction;
- EPropertyLevel m_propertyLevel;
-};
-
//------------------------------------------------------------------------------------------
// DeleteFromTextNodeCommand
//------------------------------------------------------------------------------------------
-DOM::ElementImpl *floatRefdElement(DOM::ElementImpl *element);
DOM::ElementImpl *createDefaultParagraphElement(DOM::DocumentImpl *document);
-DOM::ElementImpl *createBlockPlaceholderElement(DOM::DocumentImpl *document);
DOM::ElementImpl *createBreakElement(DOM::DocumentImpl *document);
-DOM::ElementImpl *createFontElement(DOM::DocumentImpl *document);
-DOM::ElementImpl *createStyleSpanElement(DOM::DocumentImpl *document);
bool isNodeRendered(const DOM::NodeImpl *);
bool isProbablyBlock(const DOM::NodeImpl *);
//------------------------------------------------------------------------------------------
+bool isTableStructureNode(const DOM::NodeImpl *node);
+DOM::ElementImpl *createBlockPlaceholderElement(DOM::DocumentImpl *document);
+
} // end namespace khtml
#endif