2 * Copyright (C) 2011, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include "InspectorStyleTextEditor.h"
30 #include "CSSPropertySourceData.h"
31 #include "HTMLParserIdioms.h"
32 #include "InspectorStyleSheet.h"
36 InspectorStyleTextEditor::InspectorStyleTextEditor(Vector<InspectorStyleProperty>* allProperties, Vector<InspectorStyleProperty>* disabledProperties, const String& styleText, const NewLineAndWhitespace& format)
37 : m_allProperties(allProperties)
38 , m_disabledProperties(disabledProperties)
39 , m_styleText(styleText)
44 void InspectorStyleTextEditor::insertProperty(unsigned index, const String& propertyText, unsigned styleBodyLength)
46 long propertyStart = 0;
48 bool insertLast = true;
49 if (index < m_allProperties->size()) {
50 const InspectorStyleProperty& property = m_allProperties->at(index);
51 if (property.hasSource) {
52 propertyStart = property.sourceData.range.start;
53 // If inserting before a disabled property, it should be shifted, too.
58 bool insertFirstInSource = true;
59 for (unsigned i = 0, size = m_allProperties->size(); i < index && i < size; ++i) {
60 const InspectorStyleProperty& property = m_allProperties->at(i);
61 if (property.hasSource && !property.disabled) {
62 insertFirstInSource = false;
67 bool insertLastInSource = true;
68 for (unsigned i = index, size = m_allProperties->size(); i < size; ++i) {
69 const InspectorStyleProperty& property = m_allProperties->at(i);
70 if (property.hasSource && !property.disabled) {
71 insertLastInSource = false;
76 String textToSet = propertyText;
78 int formattingPrependOffset = 0;
79 if (insertLast && !insertFirstInSource) {
80 propertyStart = styleBodyLength;
81 if (propertyStart && textToSet.length()) {
82 const UChar* characters = m_styleText.characters();
84 long curPos = propertyStart - 1; // The last position of style declaration, since propertyStart points past one.
85 while (curPos && isHTMLSpace(characters[curPos]))
87 if (curPos && characters[curPos] != ';') {
88 // Prepend a ";" to the property text if appending to a style declaration where
89 // the last property has no trailing ";".
90 textToSet.insert(";", 0);
91 formattingPrependOffset = 1;
96 const String& formatLineFeed = m_format.first;
97 const String& formatPropertyPrefix = m_format.second;
98 if (insertLastInSource) {
99 long formatPropertyPrefixLength = formatPropertyPrefix.length();
100 if (!formattingPrependOffset && (propertyStart < formatPropertyPrefixLength || m_styleText.substring(propertyStart - formatPropertyPrefixLength, formatPropertyPrefixLength) != formatPropertyPrefix)) {
101 textToSet.insert(formatPropertyPrefix, formattingPrependOffset);
102 if (!propertyStart || !isHTMLLineBreak(m_styleText[propertyStart - 1]))
103 textToSet.insert(formatLineFeed, formattingPrependOffset);
105 if (!isHTMLLineBreak(m_styleText[propertyStart]))
106 textToSet += formatLineFeed;
108 String fullPrefix = formatLineFeed + formatPropertyPrefix;
109 long fullPrefixLength = fullPrefix.length();
110 textToSet += fullPrefix;
111 if (insertFirstInSource && (propertyStart < fullPrefixLength || m_styleText.substring(propertyStart - fullPrefixLength, fullPrefixLength) != fullPrefix))
112 textToSet.insert(fullPrefix, formattingPrependOffset);
114 m_styleText.insert(textToSet, propertyStart);
116 // Recompute disabled property ranges after an inserted property.
117 long propertyLengthDelta = textToSet.length();
118 shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta);
121 void InspectorStyleTextEditor::replaceProperty(unsigned index, const String& newText)
123 ASSERT(index < m_allProperties->size());
125 const InspectorStyleProperty& property = m_allProperties->at(index);
126 long propertyStart = property.sourceData.range.start;
127 long propertyEnd = property.sourceData.range.end;
128 long oldLength = propertyEnd - propertyStart;
129 long newLength = newText.length();
130 long propertyLengthDelta = newLength - oldLength;
132 if (!property.disabled) {
133 SourceRange overwrittenRange;
134 unsigned insertedLength;
135 internalReplaceProperty(property, newText, &overwrittenRange, &insertedLength);
136 propertyLengthDelta = static_cast<long>(insertedLength) - static_cast<long>(overwrittenRange.length());
138 // Recompute subsequent disabled property ranges if acting on a non-disabled property.
139 shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta);
141 long textLength = newText.length();
142 unsigned disabledIndex = disabledIndexByOrdinal(index, false);
144 // Delete disabled property.
145 m_disabledProperties->remove(disabledIndex);
147 // Patch disabled property text.
148 m_disabledProperties->at(disabledIndex).rawText = newText;
153 void InspectorStyleTextEditor::removeProperty(unsigned index)
155 replaceProperty(index, "");
158 void InspectorStyleTextEditor::enableProperty(unsigned index)
160 ASSERT(m_allProperties->at(index).disabled);
162 unsigned disabledIndex = disabledIndexByOrdinal(index, false);
163 ASSERT(disabledIndex != UINT_MAX);
165 InspectorStyleProperty disabledProperty = m_disabledProperties->at(disabledIndex);
166 m_disabledProperties->remove(disabledIndex);
167 SourceRange removedRange;
168 unsigned insertedLength;
169 internalReplaceProperty(disabledProperty, disabledProperty.rawText, &removedRange, &insertedLength);
170 shiftDisabledProperties(disabledIndex, static_cast<long>(insertedLength) - static_cast<long>(removedRange.length()));
173 void InspectorStyleTextEditor::disableProperty(unsigned index)
175 ASSERT(!m_allProperties->at(index).disabled);
177 const InspectorStyleProperty& property = m_allProperties->at(index);
178 InspectorStyleProperty disabledProperty(property);
179 disabledProperty.setRawTextFromStyleDeclaration(m_styleText);
180 disabledProperty.disabled = true;
182 SourceRange removedRange;
183 unsigned insertedLength;
184 internalReplaceProperty(property, "", &removedRange, &insertedLength);
186 // If some preceding formatting has been removed, move the property to the start of the removed range.
187 if (property.sourceData.range.start > removedRange.start)
188 disabledProperty.sourceData.range.start = removedRange.start;
189 disabledProperty.sourceData.range.end = disabledProperty.sourceData.range.start;
191 // Add disabled property at correct position.
192 unsigned insertionIndex = disabledIndexByOrdinal(index, true);
193 if (insertionIndex == UINT_MAX)
194 m_disabledProperties->append(disabledProperty);
196 m_disabledProperties->insert(insertionIndex, disabledProperty);
197 long styleLengthDelta = -(static_cast<long>(removedRange.length()));
198 shiftDisabledProperties(insertionIndex + 1, styleLengthDelta); // Property removed from text - shift these back.
202 unsigned InspectorStyleTextEditor::disabledIndexByOrdinal(unsigned ordinal, bool canUseSubsequent)
204 unsigned disabledIndex = 0;
205 for (unsigned i = 0, size = m_allProperties->size(); i < size; ++i) {
206 if (m_allProperties->at(i).disabled) {
207 if (i == ordinal || (canUseSubsequent && i > ordinal))
208 return disabledIndex;
216 void InspectorStyleTextEditor::shiftDisabledProperties(unsigned fromIndex, long delta)
218 for (unsigned i = fromIndex, size = m_disabledProperties->size(); i < size; ++i) {
219 SourceRange& range = m_disabledProperties->at(i).sourceData.range;
220 range.start += delta;
225 void InspectorStyleTextEditor::internalReplaceProperty(const InspectorStyleProperty& property, const String& newText, SourceRange* removedRange, unsigned* insertedLength)
227 const SourceRange& range = property.sourceData.range;
228 long replaceRangeStart = range.start;
229 long replaceRangeEnd = range.end;
230 const UChar* characters = m_styleText.characters();
231 long newTextLength = newText.length();
232 String finalNewText = newText;
234 // Removing a property - remove preceding prefix.
235 String fullPrefix = m_format.first + m_format.second;
236 long fullPrefixLength = fullPrefix.length();
237 if (!newTextLength && fullPrefixLength) {
238 if (replaceRangeStart >= fullPrefixLength && m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) == fullPrefix)
239 replaceRangeStart -= fullPrefixLength;
240 } else if (newTextLength) {
241 if (isHTMLLineBreak(newText.characters()[newTextLength - 1])) {
242 // Coalesce newlines of the original and new property values (to avoid a lot of blank lines while incrementally applying property values).
243 bool foundNewline = false;
244 bool isLastNewline = false;
246 int textLength = m_styleText.length();
247 for (i = replaceRangeEnd; i < textLength && isSpaceOrNewline(characters[i]); ++i) {
248 isLastNewline = isHTMLLineBreak(characters[i]);
251 else if (foundNewline && !isLastNewline) {
256 if (foundNewline && isLastNewline)
260 if (fullPrefixLength > replaceRangeStart || m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) != fullPrefix)
261 finalNewText.insert(fullPrefix, 0);
264 int replacedLength = replaceRangeEnd - replaceRangeStart;
265 m_styleText.replace(replaceRangeStart, replacedLength, finalNewText);
266 *removedRange = SourceRange(replaceRangeStart, replaceRangeEnd);
267 *insertedLength = finalNewText.length();
270 } // namespace WebCore
272 #endif // ENABLE(INSPECTOR)