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#include "SVGException.h"
24#include "SVGPropertyTearOff.h"
25#include "SVGPropertyTraits.h"
26
27namespace WebCore {
28
29enum ListModification {
30    ListModificationUnknown = 0,
31    ListModificationInsert = 1,
32    ListModificationReplace = 2,
33    ListModificationRemove = 3,
34    ListModificationAppend = 4
35};
36
37template<typename PropertyType>
38class SVGAnimatedListPropertyTearOff;
39
40template<typename PropertyType>
41class SVGListProperty : public SVGProperty {
42public:
43    typedef SVGListProperty<PropertyType> Self;
44
45    typedef typename SVGPropertyTraits<PropertyType>::ListItemType ListItemType;
46    typedef SVGPropertyTearOff<ListItemType> ListItemTearOff;
47    typedef PassRefPtr<ListItemTearOff> PassListItemTearOff;
48    typedef SVGAnimatedListPropertyTearOff<PropertyType> AnimatedListPropertyTearOff;
49    typedef typename SVGAnimatedListPropertyTearOff<PropertyType>::ListWrapperCache ListWrapperCache;
50
51    bool canAlterList(ExceptionCode& ec) const
52    {
53        if (m_role == AnimValRole) {
54            ec = NO_MODIFICATION_ALLOWED_ERR;
55            return false;
56        }
57
58        return true;
59    }
60
61    static void detachListWrappersAndResize(ListWrapperCache* wrappers, unsigned newListSize = 0)
62    {
63        // See SVGPropertyTearOff::detachWrapper() for an explanation about what's happening here.
64        ASSERT(wrappers);
65        unsigned size = wrappers->size();
66        for (unsigned i = 0; i < size; ++i) {
67            if (ListItemTearOff* item = wrappers->at(i).get())
68                item->detachWrapper();
69        }
70
71        // Reinitialize the wrapper cache to be equal to the new values size, after the XML DOM changed the list.
72        if (newListSize)
73            wrappers->fill(0, newListSize);
74        else
75            wrappers->clear();
76    }
77
78    void detachListWrappers(unsigned newListSize)
79    {
80        detachListWrappersAndResize(m_wrappers, newListSize);
81    }
82
83    void setValuesAndWrappers(PropertyType* values, ListWrapperCache* wrappers, bool shouldOwnValues)
84    {
85        // This is only used for animVal support, to switch the underlying values & wrappers
86        // to the current animated values, once animation for a list starts.
87        ASSERT(m_values);
88        ASSERT(m_wrappers);
89        ASSERT(m_role == AnimValRole);
90        if (m_ownsValues)
91            delete m_values;
92        m_values = values;
93        m_ownsValues = shouldOwnValues;
94        m_wrappers = wrappers;
95        ASSERT(m_values->size() == m_wrappers->size());
96    }
97
98    // SVGList::clear()
99    void clearValues(ExceptionCode& ec)
100    {
101        if (!canAlterList(ec))
102            return;
103
104        m_values->clear();
105        commitChange();
106    }
107
108    void clearValuesAndWrappers(ExceptionCode& ec)
109    {
110        if (!canAlterList(ec))
111            return;
112
113        detachListWrappers(0);
114        m_values->clear();
115        commitChange();
116    }
117
118    // SVGList::numberOfItems()
119    unsigned numberOfItems() const
120    {
121        return m_values->size();
122    }
123
124    // SVGList::initialize()
125    ListItemType initializeValues(const ListItemType& newItem, ExceptionCode& ec)
126    {
127        if (!canAlterList(ec))
128            return ListItemType();
129
130        // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
131        processIncomingListItemValue(newItem, 0);
132
133        // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
134        m_values->clear();
135        m_values->append(newItem);
136
137        commitChange();
138        return newItem;
139    }
140
141    PassListItemTearOff initializeValuesAndWrappers(PassListItemTearOff passNewItem, ExceptionCode& ec)
142    {
143        ASSERT(m_wrappers);
144        if (!canAlterList(ec))
145            return 0;
146
147        // Not specified, but FF/Opera do it this way, and it's just sane.
148        if (!passNewItem) {
149            ec = SVGException::SVG_WRONG_TYPE_ERR;
150            return 0;
151        }
152
153        RefPtr<ListItemTearOff> newItem = passNewItem;
154        ASSERT(m_values->size() == m_wrappers->size());
155
156        // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
157        processIncomingListItemWrapper(newItem, 0);
158
159        // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
160        detachListWrappers(0);
161        m_values->clear();
162
163        m_values->append(newItem->propertyReference());
164        m_wrappers->append(newItem);
165
166        commitChange();
167        return newItem.release();
168    }
169
170    // SVGList::getItem()
171    bool canGetItem(unsigned index, ExceptionCode& ec)
172    {
173        if (index >= m_values->size()) {
174            ec = INDEX_SIZE_ERR;
175            return false;
176        }
177
178        return true;
179    }
180
181    ListItemType getItemValues(unsigned index, ExceptionCode& ec)
182    {
183        if (!canGetItem(index, ec))
184            return ListItemType();
185
186        // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
187        return m_values->at(index);
188    }
189
190    PassListItemTearOff getItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
191    {
192        ASSERT(m_wrappers);
193        if (!canGetItem(index, ec))
194            return 0;
195
196        // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
197        // Any changes made to the item are immediately reflected in the list.
198        ASSERT(m_values->size() == m_wrappers->size());
199        RefPtr<ListItemTearOff> wrapper = m_wrappers->at(index);
200        if (!wrapper) {
201            // Create new wrapper, which is allowed to directly modify the item in the list, w/o copying and cache the wrapper in our map.
202            // It is also associated with our animated property, so it can notify the SVG Element which holds the SVGAnimated*List
203            // that it has been modified (and thus can call svgAttributeChanged(associatedAttributeName)).
204            wrapper = ListItemTearOff::create(animatedList, UndefinedRole, m_values->at(index));
205            m_wrappers->at(index) = wrapper;
206        }
207
208        return wrapper.release();
209    }
210
211    // SVGList::insertItemBefore()
212    ListItemType insertItemBeforeValues(const ListItemType& newItem, unsigned index, ExceptionCode& ec)
213    {
214        if (!canAlterList(ec))
215            return ListItemType();
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 > m_values->size())
219            index = m_values->size();
220
221        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
222        if (!processIncomingListItemValue(newItem, &index)) {
223            // Inserting the item before itself is a no-op.
224            return newItem;
225        }
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        m_values->insert(index, newItem);
230
231        commitChange();
232        return newItem;
233    }
234
235    PassListItemTearOff insertItemBeforeValuesAndWrappers(PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
236    {
237        ASSERT(m_wrappers);
238        if (!canAlterList(ec))
239            return 0;
240
241        // Not specified, but FF/Opera do it this way, and it's just sane.
242        if (!passNewItem) {
243            ec = SVGException::SVG_WRONG_TYPE_ERR;
244            return 0;
245        }
246
247        // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
248        if (index > m_values->size())
249             index = m_values->size();
250
251        RefPtr<ListItemTearOff> newItem = passNewItem;
252        ASSERT(m_values->size() == m_wrappers->size());
253
254        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
255        if (!processIncomingListItemWrapper(newItem, &index))
256            return newItem.release();
257
258        // 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
259        // 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.
260        m_values->insert(index, newItem->propertyReference());
261
262        // Store new wrapper at position 'index', change its underlying value, so mutations of newItem, directly affect the item in the list.
263        m_wrappers->insert(index, newItem);
264
265        commitChange();
266        return newItem.release();
267    }
268
269    // SVGList::replaceItem()
270    bool canReplaceItem(unsigned index, ExceptionCode& ec)
271    {
272        if (!canAlterList(ec))
273            return false;
274
275        if (index >= m_values->size()) {
276            ec = INDEX_SIZE_ERR;
277            return false;
278        }
279
280        return true;
281    }
282
283    ListItemType replaceItemValues(const ListItemType& newItem, unsigned index, ExceptionCode& ec)
284    {
285        if (!canReplaceItem(index, ec))
286            return ListItemType();
287
288        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
289        // 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.
290        if (!processIncomingListItemValue(newItem, &index)) {
291            // Replacing the item with itself is a no-op.
292            return newItem;
293        }
294
295        if (m_values->isEmpty()) {
296            // 'newItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
297            ec = INDEX_SIZE_ERR;
298            return ListItemType();
299        }
300
301        // Update the value at the desired position 'index'.
302        m_values->at(index) = newItem;
303
304        commitChange();
305        return newItem;
306    }
307
308    PassListItemTearOff replaceItemValuesAndWrappers(PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
309    {
310        ASSERT(m_wrappers);
311        if (!canReplaceItem(index, ec))
312            return 0;
313
314        // Not specified, but FF/Opera do it this way, and it's just sane.
315        if (!passNewItem) {
316            ec = SVGException::SVG_WRONG_TYPE_ERR;
317            return 0;
318        }
319
320        ASSERT(m_values->size() == m_wrappers->size());
321        RefPtr<ListItemTearOff> newItem = passNewItem;
322
323        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
324        // 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.
325        if (!processIncomingListItemWrapper(newItem, &index))
326            return newItem.release();
327
328        if (m_values->isEmpty()) {
329            ASSERT(m_wrappers->isEmpty());
330            // 'passNewItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
331            ec = INDEX_SIZE_ERR;
332            return 0;
333        }
334
335        // Detach the existing wrapper.
336        RefPtr<ListItemTearOff> oldItem = m_wrappers->at(index);
337        if (oldItem)
338            oldItem->detachWrapper();
339
340        // Update the value and the wrapper at the desired position 'index'.
341        m_values->at(index) = newItem->propertyReference();
342        m_wrappers->at(index) = newItem;
343
344        commitChange();
345        return newItem.release();
346    }
347
348    // SVGList::removeItem()
349    bool canRemoveItem(unsigned index, ExceptionCode& ec)
350    {
351        if (!canAlterList(ec))
352            return false;
353
354        if (index >= m_values->size()) {
355            ec = INDEX_SIZE_ERR;
356            return false;
357        }
358
359        return true;
360    }
361
362    ListItemType removeItemValues(unsigned index, ExceptionCode& ec)
363    {
364        if (!canRemoveItem(index, ec))
365            return ListItemType();
366
367        ListItemType oldItem = m_values->at(index);
368        m_values->remove(index);
369
370        commitChange();
371        return oldItem;
372    }
373
374    PassListItemTearOff removeItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
375    {
376        ASSERT(m_wrappers);
377        if (!canRemoveItem(index, ec))
378            return 0;
379
380        ASSERT(m_values->size() == m_wrappers->size());
381
382        // Detach the existing wrapper.
383        RefPtr<ListItemTearOff> oldItem = m_wrappers->at(index);
384        if (!oldItem)
385            oldItem = ListItemTearOff::create(animatedList, UndefinedRole, m_values->at(index));
386
387        oldItem->detachWrapper();
388        m_wrappers->remove(index);
389        m_values->remove(index);
390
391        commitChange();
392        return oldItem.release();
393    }
394
395    // SVGList::appendItem()
396    ListItemType appendItemValues(const ListItemType& newItem, ExceptionCode& ec)
397    {
398        if (!canAlterList(ec))
399            return ListItemType();
400
401        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
402        processIncomingListItemValue(newItem, 0);
403
404        // Append the value at the end of the list.
405        m_values->append(newItem);
406
407        commitChange(ListModificationAppend);
408        return newItem;
409    }
410
411    PassListItemTearOff appendItemValuesAndWrappers(PassListItemTearOff passNewItem, ExceptionCode& ec)
412    {
413        ASSERT(m_wrappers);
414        if (!canAlterList(ec))
415            return 0;
416
417        // Not specified, but FF/Opera do it this way, and it's just sane.
418        if (!passNewItem) {
419            ec = SVGException::SVG_WRONG_TYPE_ERR;
420            return 0;
421        }
422
423        RefPtr<ListItemTearOff> newItem = passNewItem;
424        ASSERT(m_values->size() == m_wrappers->size());
425
426        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
427        processIncomingListItemWrapper(newItem, 0);
428
429        // Append the value and wrapper at the end of the list.
430        m_values->append(newItem->propertyReference());
431        m_wrappers->append(newItem);
432
433        commitChange(ListModificationAppend);
434        return newItem.release();
435    }
436
437    PropertyType& values()
438    {
439        ASSERT(m_values);
440        return *m_values;
441    }
442
443    ListWrapperCache& wrappers() const
444    {
445        ASSERT(m_wrappers);
446        return *m_wrappers;
447    }
448
449protected:
450    SVGListProperty(SVGPropertyRole role, PropertyType& values, ListWrapperCache* wrappers)
451        : m_role(role)
452        , m_ownsValues(false)
453        , m_values(&values)
454        , m_wrappers(wrappers)
455    {
456    }
457
458    virtual ~SVGListProperty()
459    {
460        if (m_ownsValues)
461            delete m_values;
462    }
463
464    virtual void commitChange() = 0;
465    virtual void commitChange(ListModification)
466    {
467        commitChange();
468    }
469
470    virtual bool processIncomingListItemValue(const ListItemType& newItem, unsigned* indexToModify) = 0;
471    virtual bool processIncomingListItemWrapper(RefPtr<ListItemTearOff>& newItem, unsigned* indexToModify) = 0;
472
473    SVGPropertyRole m_role;
474    bool m_ownsValues;
475    PropertyType* m_values;
476    ListWrapperCache* m_wrappers;
477};
478
479}
480
481#endif // SVGListProperty_h
482