| /* |
| * Copyright (C) 2011, Google 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "config.h" |
| #include "InspectorStyleTextEditor.h" |
| |
| #if ENABLE(INSPECTOR) |
| |
| #include "CSSPropertySourceData.h" |
| #include "HTMLParserIdioms.h" |
| #include "InspectorStyleSheet.h" |
| |
| namespace WebCore { |
| |
| InspectorStyleTextEditor::InspectorStyleTextEditor(Vector<InspectorStyleProperty>* allProperties, Vector<InspectorStyleProperty>* disabledProperties, const String& styleText, const NewLineAndWhitespace& format) |
| : m_allProperties(allProperties) |
| , m_disabledProperties(disabledProperties) |
| , m_styleText(styleText) |
| , m_format(format) |
| { |
| } |
| |
| void InspectorStyleTextEditor::insertProperty(unsigned index, const String& propertyText, unsigned styleBodyLength) |
| { |
| long propertyStart = 0; |
| |
| bool insertLast = true; |
| if (index < m_allProperties->size()) { |
| const InspectorStyleProperty& property = m_allProperties->at(index); |
| if (property.hasSource) { |
| propertyStart = property.sourceData.range.start; |
| // If inserting before a disabled property, it should be shifted, too. |
| insertLast = false; |
| } |
| } |
| |
| bool insertFirstInSource = true; |
| for (unsigned i = 0, size = m_allProperties->size(); i < index && i < size; ++i) { |
| const InspectorStyleProperty& property = m_allProperties->at(i); |
| if (property.hasSource && !property.disabled) { |
| insertFirstInSource = false; |
| break; |
| } |
| } |
| |
| bool insertLastInSource = true; |
| for (unsigned i = index, size = m_allProperties->size(); i < size; ++i) { |
| const InspectorStyleProperty& property = m_allProperties->at(i); |
| if (property.hasSource && !property.disabled) { |
| insertLastInSource = false; |
| break; |
| } |
| } |
| |
| String textToSet = propertyText; |
| |
| int formattingPrependOffset = 0; |
| if (insertLast && !insertFirstInSource) { |
| propertyStart = styleBodyLength; |
| if (propertyStart && textToSet.length()) { |
| const UChar* characters = m_styleText.characters(); |
| |
| long curPos = propertyStart - 1; // The last position of style declaration, since propertyStart points past one. |
| while (curPos && isHTMLSpace(characters[curPos])) |
| --curPos; |
| if (curPos && characters[curPos] != ';') { |
| // Prepend a ";" to the property text if appending to a style declaration where |
| // the last property has no trailing ";". |
| textToSet.insert(";", 0); |
| formattingPrependOffset = 1; |
| } |
| } |
| } |
| |
| const String& formatLineFeed = m_format.first; |
| const String& formatPropertyPrefix = m_format.second; |
| if (insertLastInSource) { |
| long formatPropertyPrefixLength = formatPropertyPrefix.length(); |
| if (!formattingPrependOffset && (propertyStart < formatPropertyPrefixLength || m_styleText.substring(propertyStart - formatPropertyPrefixLength, formatPropertyPrefixLength) != formatPropertyPrefix)) { |
| textToSet.insert(formatPropertyPrefix, formattingPrependOffset); |
| if (!propertyStart || !isHTMLLineBreak(m_styleText[propertyStart - 1])) |
| textToSet.insert(formatLineFeed, formattingPrependOffset); |
| } |
| if (!isHTMLLineBreak(m_styleText[propertyStart])) |
| textToSet += formatLineFeed; |
| } else { |
| String fullPrefix = formatLineFeed + formatPropertyPrefix; |
| long fullPrefixLength = fullPrefix.length(); |
| textToSet += fullPrefix; |
| if (insertFirstInSource && (propertyStart < fullPrefixLength || m_styleText.substring(propertyStart - fullPrefixLength, fullPrefixLength) != fullPrefix)) |
| textToSet.insert(fullPrefix, formattingPrependOffset); |
| } |
| m_styleText.insert(textToSet, propertyStart); |
| |
| // Recompute disabled property ranges after an inserted property. |
| long propertyLengthDelta = textToSet.length(); |
| shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta); |
| } |
| |
| void InspectorStyleTextEditor::replaceProperty(unsigned index, const String& newText) |
| { |
| ASSERT(index < m_allProperties->size()); |
| |
| const InspectorStyleProperty& property = m_allProperties->at(index); |
| long propertyStart = property.sourceData.range.start; |
| long propertyEnd = property.sourceData.range.end; |
| long oldLength = propertyEnd - propertyStart; |
| long newLength = newText.length(); |
| long propertyLengthDelta = newLength - oldLength; |
| |
| if (!property.disabled) { |
| SourceRange overwrittenRange; |
| unsigned insertedLength; |
| internalReplaceProperty(property, newText, &overwrittenRange, &insertedLength); |
| propertyLengthDelta = static_cast<long>(insertedLength) - static_cast<long>(overwrittenRange.length()); |
| |
| // Recompute subsequent disabled property ranges if acting on a non-disabled property. |
| shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta); |
| } else { |
| long textLength = newText.length(); |
| unsigned disabledIndex = disabledIndexByOrdinal(index, false); |
| if (!textLength) { |
| // Delete disabled property. |
| m_disabledProperties->remove(disabledIndex); |
| } else { |
| // Patch disabled property text. |
| m_disabledProperties->at(disabledIndex).rawText = newText; |
| } |
| } |
| } |
| |
| void InspectorStyleTextEditor::removeProperty(unsigned index) |
| { |
| replaceProperty(index, ""); |
| } |
| |
| void InspectorStyleTextEditor::enableProperty(unsigned index) |
| { |
| ASSERT(m_allProperties->at(index).disabled); |
| |
| unsigned disabledIndex = disabledIndexByOrdinal(index, false); |
| ASSERT(disabledIndex != UINT_MAX); |
| |
| InspectorStyleProperty disabledProperty = m_disabledProperties->at(disabledIndex); |
| m_disabledProperties->remove(disabledIndex); |
| SourceRange removedRange; |
| unsigned insertedLength; |
| internalReplaceProperty(disabledProperty, disabledProperty.rawText, &removedRange, &insertedLength); |
| shiftDisabledProperties(disabledIndex, static_cast<long>(insertedLength) - static_cast<long>(removedRange.length())); |
| } |
| |
| void InspectorStyleTextEditor::disableProperty(unsigned index) |
| { |
| ASSERT(!m_allProperties->at(index).disabled); |
| |
| const InspectorStyleProperty& property = m_allProperties->at(index); |
| InspectorStyleProperty disabledProperty(property); |
| disabledProperty.setRawTextFromStyleDeclaration(m_styleText); |
| disabledProperty.disabled = true; |
| |
| SourceRange removedRange; |
| unsigned insertedLength; |
| internalReplaceProperty(property, "", &removedRange, &insertedLength); |
| |
| // If some preceding formatting has been removed, move the property to the start of the removed range. |
| if (property.sourceData.range.start > removedRange.start) |
| disabledProperty.sourceData.range.start = removedRange.start; |
| disabledProperty.sourceData.range.end = disabledProperty.sourceData.range.start; |
| |
| // Add disabled property at correct position. |
| unsigned insertionIndex = disabledIndexByOrdinal(index, true); |
| if (insertionIndex == UINT_MAX) |
| m_disabledProperties->append(disabledProperty); |
| else { |
| m_disabledProperties->insert(insertionIndex, disabledProperty); |
| long styleLengthDelta = -(static_cast<long>(removedRange.length())); |
| shiftDisabledProperties(insertionIndex + 1, styleLengthDelta); // Property removed from text - shift these back. |
| } |
| } |
| |
| unsigned InspectorStyleTextEditor::disabledIndexByOrdinal(unsigned ordinal, bool canUseSubsequent) |
| { |
| unsigned disabledIndex = 0; |
| for (unsigned i = 0, size = m_allProperties->size(); i < size; ++i) { |
| if (m_allProperties->at(i).disabled) { |
| if (i == ordinal || (canUseSubsequent && i > ordinal)) |
| return disabledIndex; |
| ++disabledIndex; |
| } |
| } |
| |
| return UINT_MAX; |
| } |
| |
| void InspectorStyleTextEditor::shiftDisabledProperties(unsigned fromIndex, long delta) |
| { |
| for (unsigned i = fromIndex, size = m_disabledProperties->size(); i < size; ++i) { |
| SourceRange& range = m_disabledProperties->at(i).sourceData.range; |
| range.start += delta; |
| range.end += delta; |
| } |
| } |
| |
| void InspectorStyleTextEditor::internalReplaceProperty(const InspectorStyleProperty& property, const String& newText, SourceRange* removedRange, unsigned* insertedLength) |
| { |
| const SourceRange& range = property.sourceData.range; |
| long replaceRangeStart = range.start; |
| long replaceRangeEnd = range.end; |
| const UChar* characters = m_styleText.characters(); |
| long newTextLength = newText.length(); |
| String finalNewText = newText; |
| |
| // Removing a property - remove preceding prefix. |
| String fullPrefix = m_format.first + m_format.second; |
| long fullPrefixLength = fullPrefix.length(); |
| if (!newTextLength && fullPrefixLength) { |
| if (replaceRangeStart >= fullPrefixLength && m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) == fullPrefix) |
| replaceRangeStart -= fullPrefixLength; |
| } else if (newTextLength) { |
| if (isHTMLLineBreak(newText.characters()[newTextLength - 1])) { |
| // Coalesce newlines of the original and new property values (to avoid a lot of blank lines while incrementally applying property values). |
| bool foundNewline = false; |
| bool isLastNewline = false; |
| int i; |
| int textLength = m_styleText.length(); |
| for (i = replaceRangeEnd; i < textLength && isSpaceOrNewline(characters[i]); ++i) { |
| isLastNewline = isHTMLLineBreak(characters[i]); |
| if (isLastNewline) |
| foundNewline = true; |
| else if (foundNewline && !isLastNewline) { |
| replaceRangeEnd = i; |
| break; |
| } |
| } |
| if (foundNewline && isLastNewline) |
| replaceRangeEnd = i; |
| } |
| |
| if (fullPrefixLength > replaceRangeStart || m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) != fullPrefix) |
| finalNewText.insert(fullPrefix, 0); |
| } |
| |
| int replacedLength = replaceRangeEnd - replaceRangeStart; |
| m_styleText.replace(replaceRangeStart, replacedLength, finalNewText); |
| *removedRange = SourceRange(replaceRangeStart, replaceRangeEnd); |
| *insertedLength = finalNewText.length(); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(INSPECTOR) |