Home | History | Annotate | Download | only in properties
      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
    429