michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ChangeCSSInlineStyleTxn.h" michael@0: #include "nsAString.h" // for nsAString_internal::Append, etc michael@0: #include "nsCRT.h" // for nsCRT michael@0: #include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc michael@0: #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc michael@0: #include "nsGkAtoms.h" // for nsGkAtoms, etc michael@0: #include "nsIAtom.h" // for nsIAtom michael@0: #include "nsIDOMCSSStyleDeclaration.h" // for nsIDOMCSSStyleDeclaration michael@0: #include "nsIDOMElement.h" // for nsIDOMElement michael@0: #include "nsIDOMElementCSSInlineStyle.h" michael@0: #include "nsISupportsImpl.h" // for EditTxn::QueryInterface, etc michael@0: #include "nsISupportsUtils.h" // for NS_ADDREF michael@0: #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc michael@0: #include "nsReadableUtils.h" // for ToNewUnicode michael@0: #include "nsString.h" // for nsAutoString, nsString, etc michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsXPCOM.h" // for NS_Free michael@0: michael@0: class nsIEditor; michael@0: michael@0: #define kNullCh (char16_t('\0')) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeCSSInlineStyleTxn, EditTxn, michael@0: mElement) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeCSSInlineStyleTxn) michael@0: NS_INTERFACE_MAP_END_INHERITING(EditTxn) michael@0: michael@0: // answers true if aValue is in the string list of white-space separated values aValueList michael@0: // a case-sensitive search is performed if aCaseSensitive is true michael@0: bool michael@0: ChangeCSSInlineStyleTxn::ValueIncludes(const nsAString &aValueList, const nsAString &aValue, bool aCaseSensitive) michael@0: { michael@0: nsAutoString valueList(aValueList); michael@0: bool result = false; michael@0: michael@0: valueList.Append(kNullCh); // put an extra null at the end michael@0: michael@0: char16_t *value = ToNewUnicode(aValue); michael@0: char16_t *start = valueList.BeginWriting(); michael@0: char16_t *end = start; michael@0: michael@0: while (kNullCh != *start) { michael@0: while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space michael@0: start++; michael@0: } michael@0: end = start; michael@0: michael@0: while ((kNullCh != *end) && (false == nsCRT::IsAsciiSpace(*end))) { // look for space or end michael@0: end++; michael@0: } michael@0: *end = kNullCh; // end string here michael@0: michael@0: if (start < end) { michael@0: if (aCaseSensitive) { michael@0: if (!nsCRT::strcmp(value, start)) { michael@0: result = true; michael@0: break; michael@0: } michael@0: } michael@0: else { michael@0: if (nsDependentString(value).Equals(nsDependentString(start), michael@0: nsCaseInsensitiveStringComparator())) { michael@0: result = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: start = ++end; michael@0: } michael@0: NS_Free(value); michael@0: return result; michael@0: } michael@0: michael@0: // removes the value aRemoveValue from the string list of white-space separated values aValueList michael@0: void michael@0: ChangeCSSInlineStyleTxn::RemoveValueFromListOfValues(nsAString & aValues, const nsAString & aRemoveValue) michael@0: { michael@0: nsAutoString classStr(aValues); // copy to work buffer nsAutoString rv(aRemoveValue); michael@0: nsAutoString outString; michael@0: classStr.Append(kNullCh); // put an extra null at the end michael@0: michael@0: char16_t *start = classStr.BeginWriting(); michael@0: char16_t *end = start; michael@0: michael@0: while (kNullCh != *start) { michael@0: while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space michael@0: start++; michael@0: } michael@0: end = start; michael@0: michael@0: while ((kNullCh != *end) && (false == nsCRT::IsAsciiSpace(*end))) { // look for space or end michael@0: end++; michael@0: } michael@0: *end = kNullCh; // end string here michael@0: michael@0: if (start < end) { michael@0: if (!aRemoveValue.Equals(start)) { michael@0: outString.Append(start); michael@0: outString.Append(char16_t(' ')); michael@0: } michael@0: } michael@0: michael@0: start = ++end; michael@0: } michael@0: aValues.Assign(outString); michael@0: } michael@0: michael@0: ChangeCSSInlineStyleTxn::ChangeCSSInlineStyleTxn() michael@0: : EditTxn() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP ChangeCSSInlineStyleTxn::Init(nsIEditor *aEditor, michael@0: nsIDOMElement *aElement, michael@0: nsIAtom *aProperty, michael@0: const nsAString& aValue, michael@0: bool aRemoveProperty) michael@0: { michael@0: NS_ASSERTION(aEditor && aElement, "bad arg"); michael@0: if (!aEditor || !aElement) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: mEditor = aEditor; michael@0: mElement = do_QueryInterface(aElement); michael@0: mProperty = aProperty; michael@0: NS_ADDREF(mProperty); michael@0: mValue.Assign(aValue); michael@0: mRemoveProperty = aRemoveProperty; michael@0: mUndoAttributeWasSet = false; michael@0: mRedoAttributeWasSet = false; michael@0: mUndoValue.Truncate(); michael@0: mRedoValue.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP ChangeCSSInlineStyleTxn::DoTransaction(void) michael@0: { michael@0: NS_ASSERTION(mEditor && mElement, "bad state"); michael@0: if (!mEditor || !mElement) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: nsCOMPtr inlineStyles = do_QueryInterface(mElement); michael@0: NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr cssDecl; michael@0: nsresult result = inlineStyles->GetStyle(getter_AddRefs(cssDecl)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(cssDecl, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoString propertyNameString; michael@0: mProperty->ToString(propertyNameString); michael@0: michael@0: NS_NAMED_LITERAL_STRING(styleAttr, "style"); michael@0: result = mElement->HasAttribute(styleAttr, &mUndoAttributeWasSet); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsAutoString values; michael@0: result = cssDecl->GetPropertyValue(propertyNameString, values); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: mUndoValue.Assign(values); michael@0: michael@0: // does this property accept more than 1 value ? michael@0: // we need to know that because of bug 62682 michael@0: bool multiple = AcceptsMoreThanOneValue(mProperty); michael@0: michael@0: if (mRemoveProperty) { michael@0: nsAutoString returnString; michael@0: if (multiple) { michael@0: // the property can have more than one value, let's remove only michael@0: // the value we have to remove and not the others michael@0: michael@0: // the 2 lines below are a workaround because nsDOMCSSDeclaration::GetPropertyCSSValue michael@0: // is not yet implemented (bug 62682) michael@0: RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none")); michael@0: RemoveValueFromListOfValues(values, mValue); michael@0: if (values.IsEmpty()) { michael@0: result = cssDecl->RemoveProperty(propertyNameString, returnString); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: else { michael@0: nsAutoString priority; michael@0: result = cssDecl->GetPropertyPriority(propertyNameString, priority); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: result = cssDecl->SetProperty(propertyNameString, values, michael@0: priority); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: } michael@0: else { michael@0: result = cssDecl->RemoveProperty(propertyNameString, returnString); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: } michael@0: else { michael@0: nsAutoString priority; michael@0: result = cssDecl->GetPropertyPriority(propertyNameString, priority); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: if (multiple) { michael@0: // the property can have more than one value, let's add michael@0: // the value we have to add to the others michael@0: michael@0: // the line below is a workaround because nsDOMCSSDeclaration::GetPropertyCSSValue michael@0: // is not yet implemented (bug 62682) michael@0: AddValueToMultivalueProperty(values, mValue); michael@0: } michael@0: else michael@0: values.Assign(mValue); michael@0: result = cssDecl->SetProperty(propertyNameString, values, michael@0: priority); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: // let's be sure we don't keep an empty style attribute michael@0: uint32_t length; michael@0: result = cssDecl->GetLength(&length); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: if (!length) { michael@0: result = mElement->RemoveAttribute(styleAttr); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: else michael@0: mRedoAttributeWasSet = true; michael@0: michael@0: return cssDecl->GetPropertyValue(propertyNameString, mRedoValue); michael@0: } michael@0: michael@0: nsresult ChangeCSSInlineStyleTxn::SetStyle(bool aAttributeWasSet, michael@0: nsAString & aValue) michael@0: { michael@0: NS_ASSERTION(mEditor && mElement, "bad state"); michael@0: if (!mEditor || !mElement) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: nsresult result = NS_OK; michael@0: if (aAttributeWasSet) { michael@0: // the style attribute was set and not empty, let's recreate the declaration michael@0: nsAutoString propertyNameString; michael@0: mProperty->ToString(propertyNameString); michael@0: michael@0: nsCOMPtr inlineStyles = do_QueryInterface(mElement); michael@0: NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr cssDecl; michael@0: result = inlineStyles->GetStyle(getter_AddRefs(cssDecl)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(cssDecl, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (aValue.IsEmpty()) { michael@0: // an empty value means we have to remove the property michael@0: nsAutoString returnString; michael@0: result = cssDecl->RemoveProperty(propertyNameString, returnString); michael@0: } michael@0: else { michael@0: // let's recreate the declaration as it was michael@0: nsAutoString priority; michael@0: result = cssDecl->GetPropertyPriority(propertyNameString, priority); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: result = cssDecl->SetProperty(propertyNameString, aValue, priority); michael@0: } michael@0: } michael@0: else michael@0: result = mElement->RemoveAttribute(NS_LITERAL_STRING("style")); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP ChangeCSSInlineStyleTxn::UndoTransaction(void) michael@0: { michael@0: return SetStyle(mUndoAttributeWasSet, mUndoValue); michael@0: } michael@0: michael@0: NS_IMETHODIMP ChangeCSSInlineStyleTxn::RedoTransaction(void) michael@0: { michael@0: return SetStyle(mRedoAttributeWasSet, mRedoValue); michael@0: } michael@0: michael@0: NS_IMETHODIMP ChangeCSSInlineStyleTxn::GetTxnDescription(nsAString& aString) michael@0: { michael@0: aString.AssignLiteral("ChangeCSSInlineStyleTxn: [mRemoveProperty == "); michael@0: michael@0: if (!mRemoveProperty) michael@0: aString.AppendLiteral("false] "); michael@0: else michael@0: aString.AppendLiteral("true] "); michael@0: nsAutoString tempString; michael@0: mProperty->ToString(tempString); michael@0: aString += tempString; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // answers true if the CSS property accepts more than one value michael@0: bool michael@0: ChangeCSSInlineStyleTxn::AcceptsMoreThanOneValue(nsIAtom *aCSSProperty) michael@0: { michael@0: return aCSSProperty == nsGkAtoms::text_decoration; michael@0: } michael@0: michael@0: // adds the value aNewValue to the list of white-space separated values aValues michael@0: NS_IMETHODIMP michael@0: ChangeCSSInlineStyleTxn::AddValueToMultivalueProperty(nsAString & aValues, const nsAString & aNewValue) michael@0: { michael@0: if (aValues.IsEmpty() michael@0: || aValues.LowerCaseEqualsLiteral("none")) { michael@0: // the list of values is empty of the value is 'none' michael@0: aValues.Assign(aNewValue); michael@0: } michael@0: else if (!ValueIncludes(aValues, aNewValue, false)) { michael@0: // we already have another value but not this one; add it michael@0: aValues.Append(char16_t(' ')); michael@0: aValues.Append(aNewValue); michael@0: } michael@0: return NS_OK; michael@0: }