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 SVGListPropertyTearOff_h
21#define SVGListPropertyTearOff_h
22
23#if ENABLE(SVG)
24#include "SVGListProperty.h"
25
26namespace WebCore {
27
28template<typename PropertyType>
29class SVGListPropertyTearOff : public SVGListProperty<PropertyType> {
30public:
31    typedef SVGListProperty<PropertyType> Base;
32
33    typedef typename SVGPropertyTraits<PropertyType>::ListItemType ListItemType;
34    typedef SVGPropertyTearOff<ListItemType> ListItemTearOff;
35    typedef PassRefPtr<ListItemTearOff> PassListItemTearOff;
36    typedef SVGAnimatedListPropertyTearOff<PropertyType> AnimatedListPropertyTearOff;
37    typedef typename SVGAnimatedListPropertyTearOff<PropertyType>::ListWrapperCache ListWrapperCache;
38
39    static PassRefPtr<SVGListPropertyTearOff<PropertyType> > create(AnimatedListPropertyTearOff* animatedProperty, SVGPropertyRole role)
40    {
41        ASSERT(animatedProperty);
42        return adoptRef(new SVGListPropertyTearOff<PropertyType>(animatedProperty, role));
43    }
44
45    int removeItemFromList(ListItemTearOff* removeItem, bool shouldSynchronizeWrappers)
46    {
47        PropertyType& values = m_animatedProperty->values();
48        ListWrapperCache& wrappers = m_animatedProperty->wrappers();
49
50        // Lookup item in cache and remove its corresponding wrapper.
51        unsigned size = wrappers.size();
52        ASSERT(size == values.size());
53        for (unsigned i = 0; i < size; ++i) {
54            RefPtr<ListItemTearOff>& item = wrappers.at(i);
55            if (item != removeItem)
56                continue;
57
58            item->detachWrapper();
59            wrappers.remove(i);
60            values.remove(i);
61
62            if (shouldSynchronizeWrappers)
63                commitChange();
64
65            return i;
66        }
67
68        return -1;
69    }
70
71    // SVGList API
72    void clear(ExceptionCode& ec)
73    {
74        Base::clearValuesAndWrappers(m_animatedProperty.get(), ec);
75    }
76
77    unsigned numberOfItems() const
78    {
79        return Base::numberOfItemsValuesAndWrappers(m_animatedProperty.get());
80    }
81
82    PassListItemTearOff initialize(PassListItemTearOff passNewItem, ExceptionCode& ec)
83    {
84        return Base::initializeValuesAndWrappers(m_animatedProperty.get(), passNewItem, ec);
85    }
86
87    PassListItemTearOff getItem(unsigned index, ExceptionCode& ec)
88    {
89        return Base::getItemValuesAndWrappers(m_animatedProperty.get(), index, ec);
90    }
91
92    PassListItemTearOff insertItemBefore(PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
93    {
94        return Base::insertItemBeforeValuesAndWrappers(m_animatedProperty.get(), passNewItem, index, ec);
95    }
96
97    PassListItemTearOff replaceItem(PassListItemTearOff passNewItem, unsigned index, ExceptionCode& ec)
98    {
99        return Base::replaceItemValuesAndWrappers(m_animatedProperty.get(), passNewItem, index, ec);
100    }
101
102    PassListItemTearOff removeItem(unsigned index, ExceptionCode& ec)
103    {
104        return Base::removeItemValuesAndWrappers(m_animatedProperty.get(), index, ec);
105    }
106
107    PassListItemTearOff appendItem(PassListItemTearOff passNewItem, ExceptionCode& ec)
108    {
109        return Base::appendItemValuesAndWrappers(m_animatedProperty.get(), passNewItem, ec);
110    }
111
112protected:
113    SVGListPropertyTearOff(AnimatedListPropertyTearOff* animatedProperty, SVGPropertyRole role)
114        : SVGListProperty<PropertyType>(role)
115        , m_animatedProperty(animatedProperty)
116    {
117    }
118
119    virtual void commitChange()
120    {
121        PropertyType& values = m_animatedProperty->values();
122        ListWrapperCache& wrappers = m_animatedProperty->wrappers();
123
124        // Update existing wrappers, as the index in the values list has changed.
125        unsigned size = wrappers.size();
126        ASSERT(size == values.size());
127        for (unsigned i = 0; i < size; ++i) {
128            RefPtr<ListItemTearOff>& item = wrappers.at(i);
129            if (!item)
130                continue;
131            item->setAnimatedProperty(m_animatedProperty.get());
132            item->setValue(values.at(i));
133        }
134
135        m_animatedProperty->commitChange();
136    }
137
138    virtual void processIncomingListItemValue(const ListItemType&, unsigned*)
139    {
140        ASSERT_NOT_REACHED();
141    }
142
143    virtual void processIncomingListItemWrapper(RefPtr<ListItemTearOff>& newItem, unsigned* indexToModify)
144    {
145        SVGAnimatedProperty* animatedPropertyOfItem = newItem->animatedProperty();
146
147        // newItem has been created manually, it doesn't belong to any SVGElement.
148        // (for example: "textElement.x.baseVal.appendItem(svgsvgElement.createSVGLength())")
149        if (!animatedPropertyOfItem)
150            return;
151
152        // newItem belongs to a SVGElement, but its associated SVGAnimatedProperty is not an animated list tear off.
153        // (for example: "textElement.x.baseVal.appendItem(rectElement.width.baseVal)")
154        if (!animatedPropertyOfItem->isAnimatedListTearOff()) {
155            // We have to copy the incoming newItem, as we're not allowed to insert this tear off as is into our wrapper cache.
156            // Otherwhise we'll end up having two SVGAnimatedPropertys that operate on the same SVGPropertyTearOff. Consider the example above:
157            // SVGRectElements SVGAnimatedLength 'width' property baseVal points to the same tear off object
158            // that's inserted into SVGTextElements SVGAnimatedLengthList 'x'. textElement.x.baseVal.getItem(0).value += 150 would
159            // mutate the rectElement width _and_ the textElement x list. That's obviously wrong, take care of that.
160            newItem = ListItemTearOff::create(newItem->propertyReference());
161            return;
162        }
163
164        // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
165        // 'newItem' is already living in another list. If it's not our list, synchronize the other lists wrappers after the removal.
166        bool livesInOtherList = animatedPropertyOfItem != m_animatedProperty;
167        int removedIndex = static_cast<AnimatedListPropertyTearOff*>(animatedPropertyOfItem)->removeItemFromList(newItem.get(), livesInOtherList);
168        ASSERT(removedIndex != -1);
169
170        if (!indexToModify)
171            return;
172
173        // If the item lived in our list, adjust the insertion index.
174        if (!livesInOtherList) {
175            unsigned& index = *indexToModify;
176            // Spec: If the item is already in this list, note that the index of the item to (replace|insert before) is before the removal of the item.
177            if (static_cast<unsigned>(removedIndex) < index)
178                --index;
179        }
180    }
181
182    // Back pointer to the animated property that created us
183    // For example (text.x.baseVal): m_animatedProperty points to the 'x' SVGAnimatedLengthList object
184    RefPtr<AnimatedListPropertyTearOff> m_animatedProperty;
185};
186
187}
188
189#endif // ENABLE(SVG)
190#endif // SVGListPropertyTearOff_h
191