Reviewed by Kevin.
[WebKit-https.git] / WebCore / khtml / editing / apply_style_command.cpp
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "apply_style_command.h"
27
28 #include "html_interchange.h"
29
30 #include "css/css_valueimpl.h"
31 #include "css/css_computedstyle.h"
32 #include "css/cssparser.h"
33 #include "css/cssproperties.h"
34 #include "dom/dom_string.h"
35 #include "html/html_elementimpl.h"
36 #include "misc/htmltags.h"
37 #include "misc/htmlattrs.h"
38 #include "rendering/render_object.h"
39 #include "xml/dom_docimpl.h"
40 #include "xml/dom_textimpl.h"
41 #include "xml/dom2_rangeimpl.h"
42
43 #if APPLE_CHANGES
44 #include "KWQAssertions.h"
45 #else
46 #define ASSERT(assertion) assert(assertion)
47 #endif
48
49 using DOM::CSSComputedStyleDeclarationImpl;
50 using DOM::CSSMutableStyleDeclarationImpl;
51 using DOM::CSSParser;
52 using DOM::CSSPrimitiveValue;
53 using DOM::CSSPrimitiveValueImpl;
54 using DOM::CSSProperty;
55 using DOM::CSSStyleDeclarationImpl;
56 using DOM::CSSValue;
57 using DOM::CSSValueImpl;
58 using DOM::DOMString;
59 using DOM::DoNotUpdateLayout;
60 using DOM::DocumentImpl;
61 using DOM::ElementImpl;
62 using DOM::HTMLElementImpl;
63 using DOM::NamedAttrMapImpl;
64 using DOM::NodeImpl;
65 using DOM::Position;
66 using DOM::RangeImpl;
67 using DOM::TextImpl;
68
69 namespace khtml {
70
71 class StyleChange {
72 public:
73     enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles };
74
75     explicit StyleChange(CSSStyleDeclarationImpl *, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
76     StyleChange(CSSStyleDeclarationImpl *, const Position &, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
77
78     static ELegacyHTMLStyles styleModeForParseMode(bool);
79
80     DOMString cssStyle() const { return m_cssStyle; }
81     bool applyBold() const { return m_applyBold; }
82     bool applyItalic() const { return m_applyItalic; }
83     bool applyFontColor() const { return m_applyFontColor.length() > 0; }
84     bool applyFontFace() const { return m_applyFontFace.length() > 0; }
85     bool applyFontSize() const { return m_applyFontSize.length() > 0; }
86
87     DOMString fontColor() { return m_applyFontColor; }
88     DOMString fontFace() { return m_applyFontFace; }
89     DOMString fontSize() { return m_applyFontSize; }
90
91     bool usesLegacyStyles() const { return m_usesLegacyStyles; }
92
93 private:
94     void init(CSSStyleDeclarationImpl *, const Position &);
95     bool checkForLegacyHTMLStyleChange(const CSSProperty *);
96     static bool currentlyHasStyle(const Position &, const CSSProperty *);
97     
98     DOMString m_cssStyle;
99     bool m_applyBold;
100     bool m_applyItalic;
101     DOMString m_applyFontColor;
102     DOMString m_applyFontFace;
103     DOMString m_applyFontSize;
104     bool m_usesLegacyStyles;
105 };
106
107
108
109 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
110     : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
111 {
112     init(style, Position());
113 }
114
115 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
116     : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
117 {
118     init(style, position);
119 }
120
121 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
122 {
123     style->ref();
124     CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
125     mutableStyle->ref();
126     style->deref();
127     
128     QString styleText("");
129
130     QValueListConstIterator<CSSProperty> end;
131     for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
132         const CSSProperty *property = &*it;
133
134         // If position is empty or the position passed in already has the 
135         // style, just move on.
136         if (position.isNotNull() && currentlyHasStyle(position, property))
137             continue;
138         
139         // If needed, figure out if this change is a legacy HTML style change.
140         if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
141             continue;
142
143         // Add this property
144
145         if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
146             // we have to special-case text decorations
147             CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
148             styleText += alteredProperty.cssText().string();
149         } else {
150             styleText += property->cssText().string();
151         }
152     }
153
154     mutableStyle->deref();
155
156     // Save the result for later
157     m_cssStyle = styleText.stripWhiteSpace();
158 }
159
160 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
161 {
162     return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
163 }
164
165 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
166 {
167     if (!property || !property->value()) {
168         return false;
169     }
170     
171     DOMString valueText(property->value()->cssText());
172     switch (property->id()) {
173         case CSS_PROP_FONT_WEIGHT:
174             if (strcasecmp(valueText, "bold") == 0) {
175                 m_applyBold = true;
176                 return true;
177             }
178             break;
179         case CSS_PROP_FONT_STYLE:
180             if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
181                 m_applyItalic = true;
182                 return true;
183             }
184             break;
185         case CSS_PROP_COLOR: {
186             QColor color(CSSParser::parseColor(valueText));
187             m_applyFontColor = color.name();
188             return true;
189         }
190         case CSS_PROP_FONT_FAMILY:
191             m_applyFontFace = valueText;
192             return true;
193         case CSS_PROP_FONT_SIZE:
194             if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
195                 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
196                 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
197                 if (number <= 9)
198                     m_applyFontSize = "1";
199                 else if (number <= 10)
200                     m_applyFontSize = "2";
201                 else if (number <= 13)
202                     m_applyFontSize = "3";
203                 else if (number <= 16)
204                     m_applyFontSize = "4";
205                 else if (number <= 18)
206                     m_applyFontSize = "5";
207                 else if (number <= 24)
208                     m_applyFontSize = "6";
209                 else
210                     m_applyFontSize = "7";
211                 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write 
212                 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all, 
213                 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
214                 return false; 
215             }
216             else {
217                 // Can't make sense of the number. Put no font size.
218                 return true;
219             }
220     }
221     return false;
222 }
223
224 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
225 {
226     ASSERT(pos.isNotNull());
227     CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
228     ASSERT(style);
229     style->ref();
230     CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
231     style->deref();
232     if (!value)
233         return false;
234     value->ref();
235     bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
236     value->deref();
237     return result;
238 }
239
240 static DOMString &styleSpanClassString()
241 {
242     static DOMString styleSpanClassString = AppleStyleSpanClass;
243     return styleSpanClassString;
244 }
245
246 bool isStyleSpan(const NodeImpl *node)
247 {
248     if (!node || !node->isHTMLElement())
249         return false;
250
251     const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
252     return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
253 }
254
255 static bool isEmptyStyleSpan(const NodeImpl *node)
256 {
257     if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
258         return false;
259
260     const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
261     CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
262     return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
263 }
264
265 static bool isEmptyFontTag(const NodeImpl *node)
266 {
267     if (!node || node->id() != ID_FONT)
268         return false;
269
270     const ElementImpl *elem = static_cast<const ElementImpl *>(node);
271     NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
272     return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
273 }
274
275 static ElementImpl *createFontElement(DocumentImpl *document)
276 {
277     int exceptionCode = 0;
278     ElementImpl *fontNode = document->createHTMLElement("font", exceptionCode);
279     ASSERT(exceptionCode == 0);
280     fontNode->setAttribute(ATTR_CLASS, styleSpanClassString());
281     return fontNode;
282 }
283
284 ElementImpl *createStyleSpanElement(DocumentImpl *document)
285 {
286     int exceptionCode = 0;
287     ElementImpl *styleElement = document->createHTMLElement("SPAN", exceptionCode);
288     ASSERT(exceptionCode == 0);
289     styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
290     return styleElement;
291 }
292
293 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
294     : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
295 {   
296     ASSERT(m_style);
297     m_style->ref();
298 }
299
300 ApplyStyleCommand::~ApplyStyleCommand()
301 {
302     ASSERT(m_style);
303     m_style->deref();
304 }
305
306 void ApplyStyleCommand::doApply()
307 {
308     switch (m_propertyLevel) {
309         case PropertyDefault: {
310             // apply the block-centric properties of the style
311             CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
312             blockStyle->ref();
313             applyBlockStyle(blockStyle);
314             // apply any remaining styles to the inline elements
315             // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
316             if (blockStyle->length() < m_style->length()) {
317                 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
318                 inlineStyle->ref();
319                 applyRelativeFontStyleChange(inlineStyle);
320                 blockStyle->diff(inlineStyle);
321                 applyInlineStyle(inlineStyle);
322                 inlineStyle->deref();
323             }
324             blockStyle->deref();
325             break;
326         }
327         case ForceBlockProperties:
328             // Force all properties to be applied as block styles.
329             applyBlockStyle(m_style);
330             break;
331     }
332    
333     setEndingSelectionNeedsLayout();
334 }
335
336 EditAction ApplyStyleCommand::editingAction() const
337 {
338     return m_editingAction;
339 }
340
341 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
342 {
343     // update document layout once before removing styles
344     // so that we avoid the expense of updating before each and every call
345     // to check a computed style
346     document()->updateLayout();
347
348     // get positions we want to use for applying style
349     Position start(endingSelection().start());
350     Position end(endingSelection().end());
351     
352     // remove current values, if any, of the specified styles from the blocks
353     // NOTE: tracks the previous block to avoid repeated processing
354     // Also, gather up all the nodes we want to process in a QPtrList before
355     // doing anything. This averts any bugs iterating over these nodes
356     // once you start removing and applying style.
357     NodeImpl *beyondEnd = end.node()->traverseNextNode();
358     QPtrList<NodeImpl> nodes;
359     for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
360         nodes.append(node);
361         
362     NodeImpl *prevBlock = 0;
363     for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
364         NodeImpl *block = it.current()->enclosingBlockFlowElement();
365         if (block != prevBlock && block->isHTMLElement()) {
366             removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
367             prevBlock = block;
368         }
369     }
370     
371     // apply specified styles to the block flow elements in the selected range
372     prevBlock = 0;
373     for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
374         NodeImpl *node = it.current();
375         if (node->renderer()) {
376             NodeImpl *block = node->enclosingBlockFlowElement();
377             if (block != prevBlock) {
378                 addBlockStyleIfNeeded(style, node);
379                 prevBlock = block;
380             }
381         }
382     }
383 }
384
385 #define NoFontDelta (0.0f)
386 #define MinimumFontSize (0.1f)
387
388 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
389 {
390     if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
391         // Explicit font size overrides any delta.
392         style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
393         return;
394     }
395
396     // Get the adjustment amount out of the style.
397     CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
398     if (!value)
399         return;
400     value->ref();
401     float adjustment = NoFontDelta;
402     if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
403         CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
404         if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
405             // Only PX handled now. If we handle more types in the future, perhaps
406             // a switch statement here would be more appropriate.
407             adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
408         }
409     }
410     style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
411     value->deref();
412     if (adjustment == NoFontDelta)
413         return;
414     
415     // Adjust to the positions we want to use for applying style.
416     Selection selection = endingSelection();
417     Position start(selection.start().downstream());
418     Position end(selection.end().upstream());
419     if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
420         Position swap = start;
421         start = end;
422         end = swap;
423     }
424
425     // Join up any adjacent text nodes.
426     if (start.node()->isTextNode()) {
427         joinChildTextNodes(start.node()->parentNode(), start, end);
428         selection = endingSelection();
429         start = selection.start();
430         end = selection.end();
431     }
432     if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
433         joinChildTextNodes(end.node()->parentNode(), start, end);
434         selection = endingSelection();
435         start = selection.start();
436         end = selection.end();
437     }
438
439     // Split the start text nodes if needed to apply style.
440     bool splitStart = splitTextAtStartIfNeeded(start, end); 
441     if (splitStart) {
442         start = endingSelection().start();
443         end = endingSelection().end();
444     }
445     bool splitEnd = splitTextAtEndIfNeeded(start, end);
446     if (splitEnd) {
447         start = endingSelection().start();
448         end = endingSelection().end();
449     }
450
451     NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
452     start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
453     NodeImpl *startNode = start.node();
454     if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
455         startNode = startNode->traverseNextNode();
456
457     // Store away font size before making any changes to the document.
458     // This ensures that changes to one node won't effect another.
459     QMap<const NodeImpl *,float> startingFontSizes;
460     for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
461         startingFontSizes.insert(node, computedFontSize(node));
462
463     // These spans were added by us. If empty after font size changes, they can be removed.
464     QPtrList<NodeImpl> emptySpans;
465     
466     NodeImpl *lastStyledNode = 0;
467     for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
468         HTMLElementImpl *elem = 0;
469         if (node->isHTMLElement()) {
470             // Only work on fully selected nodes.
471             if (!nodeFullySelected(node, start, end))
472                 continue;
473             elem = static_cast<HTMLElementImpl *>(node);
474         }
475         else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
476             // Last styled node was not parent node of this text node, but we wish to style this
477             // text node. To make this possible, add a style span to surround this text node.
478             elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
479             insertNodeBefore(elem, node);
480             surroundNodeRangeWithElement(node, node, elem);
481         }
482         else {
483             // Only handle HTML elements and text nodes.
484             continue;
485         }
486         lastStyledNode = node;
487         
488         CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
489         float currentFontSize = computedFontSize(node);
490         float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
491         if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
492             inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
493             currentFontSize = computedFontSize(node);
494         }
495         if (currentFontSize != desiredFontSize) {
496             QString desiredFontSizeString = QString::number(desiredFontSize);
497             desiredFontSizeString += "px";
498             inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
499             setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
500         }
501         if (inlineStyleDecl->length() == 0) {
502             removeNodeAttribute(elem, ATTR_STYLE);
503             if (isEmptyStyleSpan(elem))
504                 emptySpans.append(elem);
505         }
506     }
507
508     for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
509         removeNodePreservingChildren(it.current());
510 }
511
512 #undef NoFontDelta
513 #undef MinimumFontSize
514
515 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
516 {
517     // adjust to the positions we want to use for applying style
518     Position start(endingSelection().start().downstream().equivalentRangeCompliantPosition());
519     Position end(endingSelection().end().upstream());
520
521     if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
522         Position swap = start;
523         start = end;
524         end = swap;
525     }
526
527     // update document layout once before removing styles
528     // so that we avoid the expense of updating before each and every call
529     // to check a computed style
530     document()->updateLayout();
531
532     // split the start node and containing element if the selection starts inside of it
533     bool splitStart = splitTextElementAtStartIfNeeded(start, end); 
534     if (splitStart) {
535         start = endingSelection().start();
536         end = endingSelection().end();
537     }
538
539     // split the end node and containing element if the selection ends inside of it
540     bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
541     start = endingSelection().start();
542     end = endingSelection().end();
543
544     // Remove style from the selection.
545     // Use the upstream position of the start for removing style.
546     // This will ensure we remove all traces of the relevant styles from the selection
547     // and prevent us from adding redundant ones, as described in:
548     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
549     removeInlineStyle(style, start.upstream(), end);
550     start = endingSelection().start();
551     end = endingSelection().end();
552
553     if (splitStart) {
554         bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
555         if (mergedStart) {
556             start = endingSelection().start();
557             end = endingSelection().end();
558         }
559     }
560
561     if (splitEnd) {
562         mergeEndWithNextIfIdentical(start, end);
563         start = endingSelection().start();
564         end = endingSelection().end();
565     }
566
567     // update document layout once before running the rest of the function
568     // so that we avoid the expense of updating before each and every call
569     // to check a computed style
570     document()->updateLayout();
571     
572     if (start.node() == end.node()) {
573         // simple case...start and end are the same node
574         addInlineStyleIfNeeded(style, start.node(), end.node());
575     }
576     else {
577         NodeImpl *node = start.node();
578         if (start.offset() >= start.node()->caretMaxOffset())
579             node = node->traverseNextNode();
580         while (1) {
581             if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
582                 NodeImpl *runStart = node;
583                 while (1) {
584                     NodeImpl *next = node->traverseNextNode();
585                     // Break if node is the end node, or if the next node does not fit in with
586                     // the current group.
587                     if (node == end.node() || 
588                         runStart->parentNode() != next->parentNode() || 
589                         (next->isHTMLElement() && next->id() != ID_BR) || 
590                         (next->renderer() && !next->renderer()->isInline()))
591                         break;
592                     node = next;
593                 }
594                 // Now apply style to the run we found.
595                 addInlineStyleIfNeeded(style, runStart, node);
596             }
597             if (node == end.node())
598                 break;
599             node = node->traverseNextNode();
600         }
601     }
602
603     if (splitStart || splitEnd) {
604         cleanUpEmptyStyleSpans(start, end);
605     }
606 }
607
608 //------------------------------------------------------------------------------------------
609 // ApplyStyleCommand: style-removal helpers
610
611 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
612 {
613     QValueListConstIterator<CSSProperty> end;
614     for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
615         switch ((*it).id()) {
616             case CSS_PROP_FONT_WEIGHT:
617                 if (elem->id() == ID_B)
618                     return true;
619                 break;
620             case CSS_PROP_FONT_STYLE:
621                 if (elem->id() == ID_I)
622                     return true;
623         }
624     }
625
626     return false;
627 }
628
629 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
630 {
631     // This node can be removed.
632     // EDIT FIXME: This does not handle the case where the node
633     // has attributes. But how often do people add attributes to <B> tags? 
634     // Not so often I think.
635     ASSERT(elem);
636     removeNodePreservingChildren(elem);
637 }
638
639 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
640 {
641     ASSERT(style);
642     ASSERT(elem);
643
644     if (elem->id() != ID_FONT)
645         return;
646
647     int exceptionCode = 0;
648     QValueListConstIterator<CSSProperty> end;
649     for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
650         switch ((*it).id()) {
651             case CSS_PROP_COLOR:
652                 elem->removeAttribute(ATTR_COLOR, exceptionCode);
653                 ASSERT(exceptionCode == 0);
654                 break;
655             case CSS_PROP_FONT_FAMILY:
656                 elem->removeAttribute(ATTR_FACE, exceptionCode);
657                 ASSERT(exceptionCode == 0);
658                 break;
659             case CSS_PROP_FONT_SIZE:
660                 elem->removeAttribute(ATTR_SIZE, exceptionCode);
661                 ASSERT(exceptionCode == 0);
662                 break;
663         }
664     }
665
666     if (isEmptyFontTag(elem))
667         removeNodePreservingChildren(elem);
668 }
669
670 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
671 {
672     ASSERT(style);
673     ASSERT(elem);
674
675     CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
676     if (!decl)
677         return;
678
679     QValueListConstIterator<CSSProperty> end;
680     for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
681         int propertyID = (*it).id();
682         CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
683         if (value) {
684             value->ref();
685             removeCSSProperty(decl, propertyID);
686             value->deref();
687         }
688     }
689
690     if (isEmptyStyleSpan(elem))
691         removeNodePreservingChildren(elem);
692 }
693
694 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
695 {
696     ASSERT(start.isNotNull());
697     ASSERT(end.isNotNull());
698     ASSERT(start.node()->inDocument());
699     ASSERT(end.node()->inDocument());
700     ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
701     
702 }
703
704 static bool hasTextDecorationProperty(NodeImpl *node)
705 {
706     if (!node->isElementNode())
707         return false;
708
709     ElementImpl *element = static_cast<ElementImpl *>(node);
710     CSSComputedStyleDeclarationImpl style(element);
711
712     CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
713
714     if (value) {
715         value->ref();
716         DOMString valueText(value->cssText());
717         value->deref();
718         if (strcasecmp(valueText,"none") != 0)
719             return true;
720     }
721
722     return false;
723 }
724
725 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
726 {
727     NodeImpl *result = NULL;
728
729     for (NodeImpl *n = node; n; n = n->parentNode()) {
730         if (hasTextDecorationProperty(n))
731             result = n;
732     }
733
734     return result;
735 }
736
737 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
738 {
739     ASSERT(node);
740     ASSERT(node->isElementNode());
741     
742     // non-html elements not handled yet
743     if (!node->isHTMLElement())
744         return 0;
745
746     HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
747     CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
748     if (!style)
749         return 0;
750
751     style->ref();
752     int properties[1] = { CSS_PROP_TEXT_DECORATION };
753     CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
754
755     CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
756     if (property && strcasecmp(property->cssText(), "none") != 0) {
757         removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
758     }
759
760     style->deref();
761
762     return textDecorationStyle;
763 }
764
765 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
766 {
767     ASSERT(node);
768     ASSERT(node->isElementNode());
769     
770     // non-html elements not handled yet
771     if (!node->isHTMLElement())
772         return 0;
773
774     HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
775     CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
776     ASSERT(computedStyle);
777
778     computedStyle->ref();
779
780     int properties[1] = { CSS_PROP_TEXT_DECORATION };
781     CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
782     
783
784     CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
785     if (property && strcasecmp(property->cssText(), "none") != 0) {
786         property->ref();
787         CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
788
789         newStyle->ref();
790         newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
791         applyTextDecorationStyle(node, newStyle);
792         newStyle->deref();
793
794         property->deref();
795     }
796
797     computedStyle->deref();
798
799     return textDecorationStyle;
800 }
801
802 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
803 {
804     ASSERT(node);
805
806     if (!style || !style->cssText().length())
807         return;
808
809     if (node->isTextNode()) {
810         HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
811         insertNodeBefore(styleSpan, node);
812         surroundNodeRangeWithElement(node, node, styleSpan);
813         node = styleSpan;
814     }
815
816     if (!node->isElementNode())
817         return;
818
819     HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
820         
821     StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
822     if (styleChange.cssStyle().length() > 0) {
823         DOMString cssText = styleChange.cssStyle();
824         CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
825         if (decl)
826             cssText += decl->cssText();
827         setNodeAttribute(element, ATTR_STYLE, cssText);
828     }
829 }
830
831 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
832 {
833     NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
834     
835     if (highestAncestor) {
836         NodeImpl *nextCurrent;
837         NodeImpl *nextChild;
838         for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
839             ASSERT(current);
840             
841             nextCurrent = NULL;
842             
843             CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
844             if (decoration)
845                 decoration->ref();
846
847             for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
848                 nextChild = child->nextSibling();
849
850                 if (node == child) {
851                     nextCurrent = child;
852                 } else if (node->isAncestor(child)) {
853                     applyTextDecorationStyle(child, decoration);
854                     nextCurrent = child;
855                 } else {
856                     applyTextDecorationStyle(child, decoration);
857                 }
858             }
859
860             if (decoration)
861                 decoration->deref();
862         }
863     }
864 }
865
866 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
867 {
868     // We need to work in two passes. First we push down any inline
869     // styles that set text decoration. Then we look for any remaining
870     // styles (caused by stylesheets) and explicitly negate text
871     // decoration while pushing down.
872
873     pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
874     document()->updateLayout();
875     pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
876
877     pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
878     document()->updateLayout();
879     pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
880 }
881
882 static int maxRangeOffset(NodeImpl *n)
883 {
884     if (DOM::offsetInCharacters(n->nodeType()))
885         return n->maxOffset();
886
887     if (n->isElementNode())
888         return n->childNodeCount();
889
890     return 1;
891 }
892
893 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
894 {
895     ASSERT(start.isNotNull());
896     ASSERT(end.isNotNull());
897     ASSERT(start.node()->inDocument());
898     ASSERT(end.node()->inDocument());
899     ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
900     
901     CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
902
903     if (textDecorationSpecialProperty) {
904         pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
905         style = style->copy();
906         style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
907     }
908
909     // The s and e variables store the positions used to set the ending selection after style removal
910     // takes place. This will help callers to recognize when either the start node or the end node
911     // are removed from the document during the work of this function.
912     Position s = start;
913     Position e = end;
914
915     NodeImpl *node = start.node();
916     while (node) {
917         NodeImpl *next = node->traverseNextNode();
918         if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
919             HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
920             NodeImpl *prev = elem->traversePreviousNodePostOrder();
921             NodeImpl *next = elem->traverseNextNode();
922             if (isHTMLStyleNode(style, elem)) {
923                 removeHTMLStyleNode(elem);
924             }
925             else {
926                 removeHTMLFontStyle(style, elem);
927                 removeCSSStyle(style, elem);
928             }
929             if (!elem->inDocument()) {
930                 if (s.node() == elem) {
931                     // Since elem must have been fully selected, and it is at the start
932                     // of the selection, it is clear we can set the new s offset to 0.
933                     ASSERT(s.offset() <= s.node()->caretMinOffset());
934                     s = Position(next, 0);
935                 }
936                 if (e.node() == elem) {
937                     // Since elem must have been fully selected, and it is at the end
938                     // of the selection, it is clear we can set the new e offset to
939                     // the max range offset of prev.
940                     ASSERT(e.offset() >= maxRangeOffset(e.node()));
941                     e = Position(prev, maxRangeOffset(prev));
942                 }
943             }
944         }
945         if (node == end.node())
946             break;
947         node = next;
948     }
949
950
951     if (textDecorationSpecialProperty) {
952         style->deref();
953     }
954     
955     ASSERT(s.node()->inDocument());
956     ASSERT(e.node()->inDocument());
957     setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
958 }
959
960 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
961 {
962     ASSERT(node);
963     ASSERT(node->isElementNode());
964
965     Position pos = Position(node, node->childNodeCount()).upstream();
966     return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
967         RangeImpl::compareBoundaryPoints(pos, end) <= 0;
968 }
969
970 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
971 {
972     ASSERT(node);
973     ASSERT(node->isElementNode());
974
975     Position pos = Position(node, node->childNodeCount()).upstream();
976     bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
977     bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
978
979     return isFullyBeforeStart || isFullyAfterEnd;
980 }
981
982
983 //------------------------------------------------------------------------------------------
984 // ApplyStyleCommand: style-application helpers
985
986 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
987 {
988     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
989         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
990         TextImpl *text = static_cast<TextImpl *>(start.node());
991         splitTextNode(text, start.offset());
992         setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
993         return true;
994     }
995     return false;
996 }
997
998 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
999 {
1000     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1001         TextImpl *text = static_cast<TextImpl *>(end.node());
1002         splitTextNode(text, end.offset());
1003         
1004         NodeImpl *prevNode = text->previousSibling();
1005         ASSERT(prevNode);
1006         NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1007         ASSERT(startNode);
1008         setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
1009         return true;
1010     }
1011     return false;
1012 }
1013
1014 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
1015 {
1016     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1017         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1018         TextImpl *text = static_cast<TextImpl *>(start.node());
1019         splitTextNodeContainingElement(text, start.offset());
1020
1021         setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1022         return true;
1023     }
1024     return false;
1025 }
1026
1027 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1028 {
1029     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1030         TextImpl *text = static_cast<TextImpl *>(end.node());
1031         splitTextNodeContainingElement(text, end.offset());
1032
1033         NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
1034         ASSERT(prevNode);
1035         NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1036         ASSERT(startNode);
1037         setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
1038         return true;
1039     }
1040     return false;
1041 }
1042
1043 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
1044 {
1045     // check that tag name and all attribute names and values are identical
1046
1047     if (!first->isElementNode())
1048         return false;
1049     
1050     if (!second->isElementNode())
1051         return false;
1052
1053     ElementImpl *firstElement = static_cast<ElementImpl *>(first);
1054     ElementImpl *secondElement = static_cast<ElementImpl *>(second);
1055     
1056     if (firstElement->id() != secondElement->id())
1057         return false;
1058
1059     NamedAttrMapImpl *firstMap = firstElement->attributes();
1060     NamedAttrMapImpl *secondMap = secondElement->attributes();
1061
1062     unsigned firstLength = firstMap->length();
1063
1064     if (firstLength != secondMap->length())
1065         return false;
1066
1067     for (unsigned i = 0; i < firstLength; i++) {
1068         DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
1069         DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
1070
1071         if (!secondAttribute || attribute->value() != secondAttribute->value())
1072             return false;
1073     }
1074     
1075     return true;
1076 }
1077
1078 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
1079 {
1080     NodeImpl *startNode = start.node();
1081     long startOffset = start.offset();
1082
1083     if (start.node()->isAtomicNode()) {
1084         if (start.offset() != 0)
1085             return false;
1086
1087         if (start.node()->previousSibling())
1088             return false;
1089
1090         startNode = start.node()->parent();
1091         startOffset = 0;
1092     }
1093
1094     if (!startNode->isElementNode())
1095         return false;
1096
1097     if (startOffset != 0)
1098         return false;
1099
1100     NodeImpl *previousSibling = startNode->previousSibling();
1101
1102     if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1103         ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
1104         ElementImpl *element = static_cast<ElementImpl *>(startNode);
1105         NodeImpl *startChild = element->firstChild();
1106         ASSERT(startChild);
1107         mergeIdenticalElements(previousElement, element);
1108
1109         long startOffsetAdjustment = startChild->nodeIndex();
1110         long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
1111
1112         setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
1113                                      Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY)); 
1114
1115         return true;
1116     }
1117
1118     return false;
1119 }
1120
1121 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
1122 {
1123     NodeImpl *endNode = end.node();
1124     int endOffset = end.offset();
1125
1126     if (endNode->isAtomicNode()) {
1127         if (endOffset < endNode->caretMaxOffset())
1128             return false;
1129
1130         unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
1131         if (end.node()->nextSibling())
1132             return false;
1133
1134         endNode = end.node()->parent();
1135         endOffset = parentLastOffset;
1136     }
1137
1138     if (!endNode->isElementNode() || endNode->id() == ID_BR)
1139         return false;
1140
1141     NodeImpl *nextSibling = endNode->nextSibling();
1142
1143     if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1144         ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
1145         ElementImpl *element = static_cast<ElementImpl *>(endNode);
1146         NodeImpl *nextChild = nextElement->firstChild();
1147
1148         mergeIdenticalElements(element, nextElement);
1149
1150         NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
1151         ASSERT(startNode);
1152
1153         int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1154
1155         setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, 
1156                                      Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
1157         return true;
1158     }
1159
1160     return false;
1161 }
1162
1163 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
1164 {
1165     NodeImpl *node;
1166     for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
1167     }
1168
1169     if (node && isEmptyStyleSpan(node->previousSibling())) {
1170         removeNodePreservingChildren(node->previousSibling());
1171     }
1172
1173     if (start.node() == end.node()) {
1174         if (start.node()->isTextNode()) {
1175             for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1176                 if (isEmptyStyleSpan(cur)) {
1177                     removeNodePreservingChildren(cur);
1178                     break;
1179                 }
1180             }
1181
1182         }
1183     } else {
1184         if (start.node()->isTextNode()) {
1185             for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
1186                 if (isEmptyStyleSpan(cur)) {
1187                     removeNodePreservingChildren(cur);
1188                     break;
1189                 }
1190             }
1191         }
1192
1193         if (end.node()->isTextNode()) {
1194             for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1195                 if (isEmptyStyleSpan(cur)) {
1196                     removeNodePreservingChildren(cur);
1197                     break;
1198                 }
1199             }
1200         }
1201     }
1202     
1203     for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
1204     }
1205     if (node && isEmptyStyleSpan(node->nextSibling())) {
1206         removeNodePreservingChildren(node->nextSibling());
1207     }
1208 }
1209
1210 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
1211 {
1212     ASSERT(startNode);
1213     ASSERT(endNode);
1214     ASSERT(element);
1215     
1216     NodeImpl *node = startNode;
1217     while (1) {
1218         NodeImpl *next = node->traverseNextNode();
1219         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1220             removeNode(node);
1221             appendNode(node, element);
1222         }
1223         if (node == endNode)
1224             break;
1225         node = next;
1226     }
1227 }
1228
1229 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
1230 {
1231     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1232     // inline content.
1233     if (!node)
1234         return;
1235     
1236     HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1237     if (!block)
1238         return;
1239         
1240     StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1241     if (styleChange.cssStyle().length() > 0) {
1242         moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
1243         block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1244         DOMString cssText = styleChange.cssStyle();
1245         CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
1246         if (decl)
1247             cssText += decl->cssText();
1248         setNodeAttribute(block, ATTR_STYLE, cssText);
1249     }
1250 }
1251
1252 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
1253 {
1254     StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1255     int exceptionCode = 0;
1256     
1257     //
1258     // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1259     //
1260     if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1261         ElementImpl *fontElement = createFontElement(document());
1262         ASSERT(exceptionCode == 0);
1263         insertNodeBefore(fontElement, startNode);
1264         if (styleChange.applyFontColor())
1265             fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
1266         if (styleChange.applyFontFace())
1267             fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
1268         if (styleChange.applyFontSize())
1269             fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
1270         surroundNodeRangeWithElement(startNode, endNode, fontElement);
1271     }
1272
1273     if (styleChange.cssStyle().length() > 0) {
1274         ElementImpl *styleElement = createStyleSpanElement(document());
1275         styleElement->ref();
1276         styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
1277         insertNodeBefore(styleElement, startNode);
1278         styleElement->deref();
1279         surroundNodeRangeWithElement(startNode, endNode, styleElement);
1280     }
1281
1282     if (styleChange.applyBold()) {
1283         ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
1284         ASSERT(exceptionCode == 0);
1285         insertNodeBefore(boldElement, startNode);
1286         surroundNodeRangeWithElement(startNode, endNode, boldElement);
1287     }
1288
1289     if (styleChange.applyItalic()) {
1290         ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
1291         ASSERT(exceptionCode == 0);
1292         insertNodeBefore(italicElement, startNode);
1293         surroundNodeRangeWithElement(startNode, endNode, italicElement);
1294     }
1295 }
1296
1297 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
1298 {
1299     float size = 0.0f;
1300     
1301     if (!node)
1302         return size;
1303     
1304     Position pos(const_cast<NodeImpl *>(node), 0);
1305     CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
1306     if (!computedStyle)
1307         return size;
1308     computedStyle->ref();
1309
1310     CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
1311     if (value) {
1312         value->ref();
1313         size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1314         value->deref();
1315     }
1316
1317     computedStyle->deref();
1318     return size;
1319 }
1320
1321 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
1322 {
1323     if (!node)
1324         return;
1325
1326     Position newStart = start;
1327     Position newEnd = end;
1328     
1329     NodeImpl *child = node->firstChild();
1330     while (child) {
1331         NodeImpl *next = child->nextSibling();
1332         if (child->isTextNode() && next && next->isTextNode()) {
1333             TextImpl *childText = static_cast<TextImpl *>(child);
1334             TextImpl *nextText = static_cast<TextImpl *>(next);
1335             if (next == start.node())
1336                 newStart = Position(childText, childText->length() + start.offset());
1337             if (next == end.node())
1338                 newEnd = Position(childText, childText->length() + end.offset());
1339             DOMString textToMove = nextText->data();
1340             insertTextIntoNode(childText, childText->length(), textToMove);
1341             removeNode(next);
1342             // don't move child node pointer. it may want to merge with more text nodes.
1343         }
1344         else {
1345             child = child->nextSibling();
1346         }
1347     }
1348
1349     setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
1350 }
1351
1352 } // namespace khtml