/src/mozilla-central/editor/libeditor/ChangeStyleTransaction.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "mozilla/ChangeStyleTransaction.h" |
7 | | |
8 | | #include "mozilla/dom/Element.h" // for Element |
9 | | #include "nsAString.h" // for nsAString::Append, etc. |
10 | | #include "nsCRT.h" // for nsCRT::IsAsciiSpace |
11 | | #include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc. |
12 | | #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. |
13 | | #include "nsGkAtoms.h" // for nsGkAtoms, etc. |
14 | | #include "nsICSSDeclaration.h" // for nsICSSDeclaration. |
15 | | #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc. |
16 | | #include "nsReadableUtils.h" // for ToNewUnicode |
17 | | #include "nsString.h" // for nsAutoString, nsString, etc. |
18 | | #include "nsStyledElement.h" // for nsStyledElement. |
19 | | #include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator |
20 | | |
21 | | namespace mozilla { |
22 | | |
23 | | using namespace dom; |
24 | | |
25 | | // static |
26 | | already_AddRefed<ChangeStyleTransaction> |
27 | | ChangeStyleTransaction::Create(Element& aElement, |
28 | | nsAtom& aProperty, |
29 | | const nsAString& aValue) |
30 | 0 | { |
31 | 0 | RefPtr<ChangeStyleTransaction> transaction = |
32 | 0 | new ChangeStyleTransaction(aElement, aProperty, aValue, false); |
33 | 0 | return transaction.forget(); |
34 | 0 | } |
35 | | |
36 | | // static |
37 | | already_AddRefed<ChangeStyleTransaction> |
38 | | ChangeStyleTransaction::CreateToRemove(Element& aElement, |
39 | | nsAtom& aProperty, |
40 | | const nsAString& aValue) |
41 | 0 | { |
42 | 0 | RefPtr<ChangeStyleTransaction> transaction = |
43 | 0 | new ChangeStyleTransaction(aElement, aProperty, aValue, true); |
44 | 0 | return transaction.forget(); |
45 | 0 | } |
46 | | |
47 | | ChangeStyleTransaction::ChangeStyleTransaction(Element& aElement, |
48 | | nsAtom& aProperty, |
49 | | const nsAString& aValue, |
50 | | bool aRemove) |
51 | | : EditTransactionBase() |
52 | | , mElement(&aElement) |
53 | | , mProperty(&aProperty) |
54 | | , mValue(aValue) |
55 | | , mRemoveProperty(aRemove) |
56 | | , mUndoValue() |
57 | | , mRedoValue() |
58 | | , mUndoAttributeWasSet(false) |
59 | | , mRedoAttributeWasSet(false) |
60 | 0 | { |
61 | 0 | } |
62 | | |
63 | 0 | #define kNullCh (char16_t('\0')) |
64 | | |
65 | | NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase, |
66 | | mElement) |
67 | | |
68 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction) |
69 | 0 | NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) |
70 | | |
71 | | NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase) |
72 | | NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase) |
73 | | |
74 | | ChangeStyleTransaction::~ChangeStyleTransaction() |
75 | 0 | { |
76 | 0 | } |
77 | | |
78 | | // Answers true if aValue is in the string list of white-space separated values |
79 | | // aValueList. |
80 | | bool |
81 | | ChangeStyleTransaction::ValueIncludes(const nsAString& aValueList, |
82 | | const nsAString& aValue) |
83 | 0 | { |
84 | 0 | nsAutoString valueList(aValueList); |
85 | 0 | bool result = false; |
86 | 0 |
|
87 | 0 | // put an extra null at the end |
88 | 0 | valueList.Append(kNullCh); |
89 | 0 |
|
90 | 0 | char16_t* value = ToNewUnicode(aValue); |
91 | 0 | char16_t* start = valueList.BeginWriting(); |
92 | 0 | char16_t* end = start; |
93 | 0 |
|
94 | 0 | while (kNullCh != *start) { |
95 | 0 | while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) { |
96 | 0 | // skip leading space |
97 | 0 | start++; |
98 | 0 | } |
99 | 0 | end = start; |
100 | 0 |
|
101 | 0 | while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) { |
102 | 0 | // look for space or end |
103 | 0 | end++; |
104 | 0 | } |
105 | 0 | // end string here |
106 | 0 | *end = kNullCh; |
107 | 0 |
|
108 | 0 | if (start < end) { |
109 | 0 | if (nsDependentString(value).Equals(nsDependentString(start), |
110 | 0 | nsCaseInsensitiveStringComparator())) { |
111 | 0 | result = true; |
112 | 0 | break; |
113 | 0 | } |
114 | 0 | } |
115 | 0 | start = ++end; |
116 | 0 | } |
117 | 0 | free(value); |
118 | 0 | return result; |
119 | 0 | } |
120 | | |
121 | | // Removes the value aRemoveValue from the string list of white-space separated |
122 | | // values aValueList |
123 | | void |
124 | | ChangeStyleTransaction::RemoveValueFromListOfValues( |
125 | | nsAString& aValues, |
126 | | const nsAString& aRemoveValue) |
127 | 0 | { |
128 | 0 | nsAutoString classStr(aValues); |
129 | 0 | nsAutoString outString; |
130 | 0 | // put an extra null at the end |
131 | 0 | classStr.Append(kNullCh); |
132 | 0 |
|
133 | 0 | char16_t* start = classStr.BeginWriting(); |
134 | 0 | char16_t* end = start; |
135 | 0 |
|
136 | 0 | while (kNullCh != *start) { |
137 | 0 | while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) { |
138 | 0 | // skip leading space |
139 | 0 | start++; |
140 | 0 | } |
141 | 0 | end = start; |
142 | 0 |
|
143 | 0 | while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) { |
144 | 0 | // look for space or end |
145 | 0 | end++; |
146 | 0 | } |
147 | 0 | // end string here |
148 | 0 | *end = kNullCh; |
149 | 0 |
|
150 | 0 | if (start < end && !aRemoveValue.Equals(start)) { |
151 | 0 | outString.Append(start); |
152 | 0 | outString.Append(char16_t(' ')); |
153 | 0 | } |
154 | 0 |
|
155 | 0 | start = ++end; |
156 | 0 | } |
157 | 0 | aValues.Assign(outString); |
158 | 0 | } |
159 | | |
160 | | NS_IMETHODIMP |
161 | | ChangeStyleTransaction::DoTransaction() |
162 | 0 | { |
163 | 0 | nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement); |
164 | 0 | NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); |
165 | 0 |
|
166 | 0 | nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style(); |
167 | 0 | |
168 | 0 | nsAutoString propertyNameString; |
169 | 0 | mProperty->ToString(propertyNameString); |
170 | 0 |
|
171 | 0 | mUndoAttributeWasSet = mElement->HasAttr(kNameSpaceID_None, |
172 | 0 | nsGkAtoms::style); |
173 | 0 |
|
174 | 0 | nsAutoString values; |
175 | 0 | nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values); |
176 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
177 | 0 | mUndoValue.Assign(values); |
178 | 0 |
|
179 | 0 | // Does this property accept more than one value? (bug 62682) |
180 | 0 | bool multiple = AcceptsMoreThanOneValue(*mProperty); |
181 | 0 |
|
182 | 0 | if (mRemoveProperty) { |
183 | 0 | nsAutoString returnString; |
184 | 0 | if (multiple) { |
185 | 0 | // Let's remove only the value we have to remove and not the others |
186 | 0 | RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none")); |
187 | 0 | RemoveValueFromListOfValues(values, mValue); |
188 | 0 | if (values.IsEmpty()) { |
189 | 0 | rv = cssDecl->RemoveProperty(propertyNameString, returnString); |
190 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
191 | 0 | } else { |
192 | 0 | nsAutoString priority; |
193 | 0 | cssDecl->GetPropertyPriority(propertyNameString, priority); |
194 | 0 | rv = cssDecl->SetProperty(propertyNameString, values, priority); |
195 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
196 | 0 | } |
197 | 0 | } else { |
198 | 0 | rv = cssDecl->RemoveProperty(propertyNameString, returnString); |
199 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
200 | 0 | } |
201 | 0 | } else { |
202 | 0 | nsAutoString priority; |
203 | 0 | cssDecl->GetPropertyPriority(propertyNameString, priority); |
204 | 0 | if (multiple) { |
205 | 0 | // Let's add the value we have to add to the others |
206 | 0 | AddValueToMultivalueProperty(values, mValue); |
207 | 0 | } else { |
208 | 0 | values.Assign(mValue); |
209 | 0 | } |
210 | 0 | rv = cssDecl->SetProperty(propertyNameString, values, priority); |
211 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
212 | 0 | } |
213 | 0 |
|
214 | 0 | // Let's be sure we don't keep an empty style attribute |
215 | 0 | uint32_t length = cssDecl->Length(); |
216 | 0 | if (!length) { |
217 | 0 | rv = mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true); |
218 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
219 | 0 | } else { |
220 | 0 | mRedoAttributeWasSet = true; |
221 | 0 | } |
222 | 0 |
|
223 | 0 | return cssDecl->GetPropertyValue(propertyNameString, mRedoValue); |
224 | 0 | } |
225 | | |
226 | | nsresult |
227 | | ChangeStyleTransaction::SetStyle(bool aAttributeWasSet, |
228 | | nsAString& aValue) |
229 | 0 | { |
230 | 0 | if (aAttributeWasSet) { |
231 | 0 | // The style attribute was not empty, let's recreate the declaration |
232 | 0 | nsAutoString propertyNameString; |
233 | 0 | mProperty->ToString(propertyNameString); |
234 | 0 |
|
235 | 0 | nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement); |
236 | 0 | NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); |
237 | 0 | nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style(); |
238 | 0 |
|
239 | 0 | if (aValue.IsEmpty()) { |
240 | 0 | // An empty value means we have to remove the property |
241 | 0 | nsAutoString returnString; |
242 | 0 | return cssDecl->RemoveProperty(propertyNameString, returnString); |
243 | 0 | } |
244 | 0 | // Let's recreate the declaration as it was |
245 | 0 | nsAutoString priority; |
246 | 0 | cssDecl->GetPropertyPriority(propertyNameString, priority); |
247 | 0 | return cssDecl->SetProperty(propertyNameString, aValue, priority); |
248 | 0 | } |
249 | 0 | return mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true); |
250 | 0 | } |
251 | | |
252 | | NS_IMETHODIMP |
253 | | ChangeStyleTransaction::UndoTransaction() |
254 | 0 | { |
255 | 0 | return SetStyle(mUndoAttributeWasSet, mUndoValue); |
256 | 0 | } |
257 | | |
258 | | NS_IMETHODIMP |
259 | | ChangeStyleTransaction::RedoTransaction() |
260 | 0 | { |
261 | 0 | return SetStyle(mRedoAttributeWasSet, mRedoValue); |
262 | 0 | } |
263 | | |
264 | | // True if the CSS property accepts more than one value |
265 | | bool |
266 | | ChangeStyleTransaction::AcceptsMoreThanOneValue(nsAtom& aCSSProperty) |
267 | 0 | { |
268 | 0 | return &aCSSProperty == nsGkAtoms::text_decoration; |
269 | 0 | } |
270 | | |
271 | | // Adds the value aNewValue to the list of white-space separated values aValues |
272 | | void |
273 | | ChangeStyleTransaction::AddValueToMultivalueProperty(nsAString& aValues, |
274 | | const nsAString& aNewValue) |
275 | 0 | { |
276 | 0 | if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) { |
277 | 0 | aValues.Assign(aNewValue); |
278 | 0 | } else if (!ValueIncludes(aValues, aNewValue)) { |
279 | 0 | // We already have another value but not this one; add it |
280 | 0 | aValues.Append(char16_t(' ')); |
281 | 0 | aValues.Append(aNewValue); |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | | } // namespace mozilla |