/* * 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* allProperties, Vector* 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(insertedLength) - static_cast(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(insertedLength) - static_cast(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(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)