initial import
[vuplus_webkit] / Source / WebCore / svg / properties / SVGListProperty.h
1 /*
2  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifndef SVGListProperty_h
21 #define SVGListProperty_h
22
23 #if ENABLE(SVG)
24 #include "SVGAnimatedProperty.h"
25 #include "SVGException.h"
26 #include "SVGPropertyTearOff.h"
27 #include "SVGPropertyTraits.h"
28
29 namespace WebCore {
30
31 template<typename PropertyType>
32 class SVGAnimatedListPropertyTearOff;
33
34 template<typename PropertyType>
35 class SVGListProperty : public SVGProperty {
36 public:
37     typedef SVGListProperty<PropertyType> Self;
38
39     typedef typename SVGPropertyTraits<PropertyType>::ListItemType ListItemType;
40     typedef SVGPropertyTearOff<ListItemType> ListItemTearOff;
41     typedef PassRefPtr<ListItemTearOff> PassListItemTearOff;
42     typedef SVGAnimatedListPropertyTearOff<PropertyType> AnimatedListPropertyTearOff;
43     typedef typename SVGAnimatedListPropertyTearOff<PropertyType>::ListWrapperCache ListWrapperCache;
44
45     bool canAlterList(ExceptionCode& ec) const
46     {
47         if (m_role == AnimValRole) {
48             ec = NO_MODIFICATION_ALLOWED_ERR;
49             return false;
50         }
51
52         return true;
53     }
54
55     // SVGList::clear()
56     void clearValues(PropertyType& values, ExceptionCode& ec)
57     {
58         if (!canAlterList(ec))
59             return;
60
61         values.clear();
62         commitChange();
63     }
64
65     void clearValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, ExceptionCode& ec)
66     {
67         ASSERT(animatedList);
68         if (!canAlterList(ec))
69             return;
70
71         animatedList->detachListWrappers(0);
72         animatedList->values().clear();
73         commitChange();
74     }
75
76     // SVGList::numberOfItems()
77     unsigned numberOfItemsValues(PropertyType& values) const
78     {
79         return values.size();
80     }
81
82     unsigned numberOfItemsValuesAndWrappers(AnimatedListPropertyTearOff* animatedList) const
83     {
84         ASSERT(animatedList);
85         return animatedList->values().size();
86     }
87
88     // SVGList::initialize()
89     ListItemType initializeValues(PropertyType& values, const ListItemType& newItem, ExceptionCode& ec)
90     {
91         if (!canAlterList(ec))
92             return ListItemType();
93
94         // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
95         processIncomingListItemValue(newItem, 0);
96
97         // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
98         values.clear();
99         values.append(newItem);
100
101         commitChange();
102         return newItem;
103     }
104
105     PassListItemTearOff initializeValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, PassListItemTearOff passNewItem, ExceptionCode& ec)
106     {
107         ASSERT(animatedList);
108         if (!canAlterList(ec))
109             return 0;
110
111         // Not specified, but FF/Opera do it this way, and it's just sane.
112         if (!passNewItem) {
113             ec = SVGException::SVG_WRONG_TYPE_ERR;
114             return 0;
115         }
116
117         PropertyType& values = animatedList->values();
118         ListWrapperCache& wrappers = animatedList->wrappers();
119
120         RefPtr<ListItemTearOff> newItem = passNewItem;
121         ASSERT(values.size() == wrappers.size());
122
123         // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
124         processIncomingListItemWrapper(newItem, 0);
125
126         // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
127         animatedList->detachListWrappers(0);
128         values.clear();
129
130         values.append(newItem->propertyReference());
131         wrappers.append(newItem);
132
133         commitChange();
134         return newItem.release();
135     }
136
137     // SVGList::getItem()
138     bool canGetItem(PropertyType& values, unsigned index, ExceptionCode& ec)
139     {
140         if (index >= values.size()) {
141             ec = INDEX_SIZE_ERR;
142             return false;
143         }
144
145         return true;
146     }
147
148     ListItemType getItemValues(PropertyType& values, unsigned index, ExceptionCode& ec)
149     {
150         if (!canGetItem(values, index, ec)) 
151             return ListItemType();
152
153         // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
154         return values.at(index);
155     }
156
157     PassListItemTearOff getItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
158     {
159         ASSERT(animatedList);
160         PropertyType& values = animatedList->values();
161         if (!canGetItem(values, index, ec))
162             return 0;
163
164         ListWrapperCache& wrappers = animatedList->wrappers();
165
166         // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
167         // Any changes made to the item are immediately reflected in the list.
168         ASSERT(values.size() == wrappers.size());
169         RefPtr<ListItemTearOff> wrapper = wrappers.at(index);
170         if (!wrapper) {
171             // Create new wrapper, which is allowed to directly modify the item in the list, w/o copying and cache the wrapper in our map.
172             // It is also associated with our animated property, so it can notify the SVG Element which holds the SVGAnimated*List
173             // that it has been modified (and thus can call svgAttributeChanged(associatedAttributeName)).
174             wrapper = ListItemTearOff::create(animatedList, UndefinedRole, values.at(index));
175             wrappers.at(index) = wrapper;
176         }
177
178         return wrapper.release();
179     }
180
181     // SVGList::insertItemBefore()
182     ListItemType insertItemBeforeValues(PropertyType& values, const ListItemType& newItem, unsigned index, ExceptionCode& ec)
183     {
184         if (!canAlterList(ec))
185             return ListItemType();
186
187         // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
188         if (index > values.size())
189             index = values.size();
190
191         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
192         processIncomingListItemValue(newItem, &index);
193
194         // Spec: Inserts a new item into the list at the specified position. The index of the item before which the new item is to be
195         // inserted. The first item is number 0. If the index is equal to 0, then the new item is inserted at the front of the list.
196         values.insert(index, newItem);
197
198         commitChange();
199         return newItem;
200     }
201
202     PassListItemTearOff insertItemBeforeValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
203     {
204         ASSERT(animatedList);
205         if (!canAlterList(ec))
206             return 0;
207
208         // Not specified, but FF/Opera do it this way, and it's just sane.
209         if (!passNewItem) {
210             ec = SVGException::SVG_WRONG_TYPE_ERR;
211             return 0;
212         }
213
214         PropertyType& values = animatedList->values();
215         ListWrapperCache& wrappers = animatedList->wrappers();
216
217         // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
218         if (index > values.size())
219              index = values.size();
220
221         RefPtr<ListItemTearOff> newItem = passNewItem;
222         ASSERT(values.size() == wrappers.size());
223
224         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
225         processIncomingListItemWrapper(newItem, &index);
226
227         // Spec: Inserts a new item into the list at the specified position. The index of the item before which the new item is to be
228         // inserted. The first item is number 0. If the index is equal to 0, then the new item is inserted at the front of the list.
229         values.insert(index, newItem->propertyReference());
230
231         // Store new wrapper at position 'index', change its underlying value, so mutations of newItem, directly affect the item in the list.
232         wrappers.insert(index, newItem);
233
234         commitChange();
235         return newItem.release();
236     }
237
238     // SVGList::replaceItem()
239     bool canReplaceItem(PropertyType& values, unsigned index, ExceptionCode& ec)
240     {
241         if (!canAlterList(ec))
242             return false;
243
244         if (index >= values.size()) {
245             ec = INDEX_SIZE_ERR;
246             return false;
247         }
248
249         return true;
250     }
251
252     ListItemType replaceItemValues(PropertyType& values, const ListItemType& newItem, unsigned index, ExceptionCode& ec)
253     {
254         if (!canReplaceItem(values, index, ec))
255             return ListItemType();
256
257         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
258         // Spec: If the item is already in this list, note that the index of the item to replace is before the removal of the item.
259         processIncomingListItemValue(newItem, &index);
260
261         if (values.isEmpty()) {
262             // 'newItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
263             ec = INDEX_SIZE_ERR;
264             return ListItemType();
265         }
266
267         // Update the value at the desired position 'index'. 
268         values.at(index) = newItem;
269
270         commitChange();
271         return newItem;
272     }
273
274     PassListItemTearOff replaceItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
275     {
276         ASSERT(animatedList);
277         PropertyType& values = animatedList->values();
278         if (!canReplaceItem(values, index, ec))
279             return 0;
280
281         // Not specified, but FF/Opera do it this way, and it's just sane.
282         if (!passNewItem) {
283             ec = SVGException::SVG_WRONG_TYPE_ERR;
284             return 0;
285         }
286
287         ListWrapperCache& wrappers = animatedList->wrappers();
288         ASSERT(values.size() == wrappers.size());
289         RefPtr<ListItemTearOff> newItem = passNewItem;
290
291         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
292         // Spec: If the item is already in this list, note that the index of the item to replace is before the removal of the item.
293         processIncomingListItemWrapper(newItem, &index);
294
295         if (values.isEmpty()) {
296             ASSERT(wrappers.isEmpty());
297             // 'passNewItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
298             ec = INDEX_SIZE_ERR;
299             return 0;
300         }
301
302         // Detach the existing wrapper.
303         RefPtr<ListItemTearOff> oldItem = wrappers.at(index);
304         if (oldItem)
305             oldItem->detachWrapper();
306
307         // Update the value and the wrapper at the desired position 'index'. 
308         values.at(index) = newItem->propertyReference();
309         wrappers.at(index) = newItem;
310
311         commitChange();
312         return newItem.release();
313     }
314
315     // SVGList::removeItem()
316     bool canRemoveItem(PropertyType& values, unsigned index, ExceptionCode& ec)
317     {
318         if (!canAlterList(ec))
319             return false;
320
321         if (index >= values.size()) {
322             ec = INDEX_SIZE_ERR;
323             return false;
324         }
325
326         return true;
327     }
328
329     ListItemType removeItemValues(PropertyType& values, unsigned index, ExceptionCode& ec)
330     {
331         if (!canRemoveItem(values, index, ec))
332             return ListItemType();
333
334         ListItemType oldItem = values.at(index);
335         values.remove(index);
336
337         commitChange();
338         return oldItem;
339     }
340
341     PassListItemTearOff removeItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
342     {
343         ASSERT(animatedList);
344         PropertyType& values = animatedList->values();
345         if (!canRemoveItem(values, index, ec))
346             return 0;
347
348         ListWrapperCache& wrappers = animatedList->wrappers();
349         ASSERT(values.size() == wrappers.size());
350
351         // Detach the existing wrapper.
352         RefPtr<ListItemTearOff> oldItem = wrappers.at(index);
353         if (!oldItem)
354             oldItem = ListItemTearOff::create(animatedList, UndefinedRole, values.at(index));
355
356         oldItem->detachWrapper();
357         wrappers.remove(index);
358         values.remove(index);
359
360         commitChange();
361         return oldItem.release();
362     }
363
364     // SVGList::appendItem()
365     ListItemType appendItemValues(PropertyType& values, const ListItemType& newItem, ExceptionCode& ec)
366     {
367         if (!canAlterList(ec))
368             return ListItemType();
369
370         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
371         processIncomingListItemValue(newItem, 0);
372
373         // Append the value at the end of the list.
374         values.append(newItem);
375
376         commitChange();
377         return newItem;
378     }
379
380     PassListItemTearOff appendItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, PassListItemTearOff passNewItem, ExceptionCode& ec)
381     {
382         ASSERT(animatedList);
383         if (!canAlterList(ec))
384             return 0;
385
386         // Not specified, but FF/Opera do it this way, and it's just sane.
387         if (!passNewItem) {
388             ec = SVGException::SVG_WRONG_TYPE_ERR;
389             return 0;
390         }
391
392         PropertyType& values = animatedList->values();
393         ListWrapperCache& wrappers = animatedList->wrappers();
394
395         RefPtr<ListItemTearOff> newItem = passNewItem;
396         ASSERT(values.size() == wrappers.size());
397
398         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
399         processIncomingListItemWrapper(newItem, 0);
400
401         // Append the value and wrapper at the end of the list.
402         values.append(newItem->propertyReference());
403         wrappers.append(newItem);
404
405         commitChange();
406         return newItem.release();
407     }
408
409     virtual SVGPropertyRole role() const { return m_role; }
410
411 protected:
412     SVGListProperty(SVGPropertyRole role)
413         : m_role(role)
414     {
415     }
416
417     virtual void commitChange() = 0;
418     virtual void processIncomingListItemValue(const ListItemType& newItem, unsigned* indexToModify) = 0;
419     virtual void processIncomingListItemWrapper(RefPtr<ListItemTearOff>& newItem, unsigned* indexToModify) = 0;
420
421 private:
422     SVGPropertyRole m_role;
423 };
424
425 }
426
427 #endif // ENABLE(SVG)
428 #endif // SVGListProperty_h