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 canAlterList(ExceptionCode & ec)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() clearValues(PropertyType & values,ExceptionCode & ec)56 void clearValues(PropertyType& values, ExceptionCode& ec) 57 { 58 if (!canAlterList(ec)) 59 return; 60 61 values.clear(); 62 commitChange(); 63 } 64 clearValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,ExceptionCode & ec)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() numberOfItemsValues(PropertyType & values)77 unsigned numberOfItemsValues(PropertyType& values) const 78 { 79 return values.size(); 80 } 81 numberOfItemsValuesAndWrappers(AnimatedListPropertyTearOff * animatedList)82 unsigned numberOfItemsValuesAndWrappers(AnimatedListPropertyTearOff* animatedList) const 83 { 84 ASSERT(animatedList); 85 return animatedList->values().size(); 86 } 87 88 // SVGList::initialize() initializeValues(PropertyType & values,const ListItemType & newItem,ExceptionCode & ec)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 initializeValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,PassListItemTearOff passNewItem,ExceptionCode & ec)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() canGetItem(PropertyType & values,unsigned index,ExceptionCode & ec)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 getItemValues(PropertyType & values,unsigned index,ExceptionCode & ec)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 getItemValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,unsigned index,ExceptionCode & ec)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() insertItemBeforeValues(PropertyType & values,const ListItemType & newItem,unsigned index,ExceptionCode & ec)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 insertItemBeforeValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,PassListItemTearOff passNewItem,unsigned index,ExceptionCode & ec)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() canReplaceItem(PropertyType & values,unsigned index,ExceptionCode & ec)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 replaceItemValues(PropertyType & values,const ListItemType & newItem,unsigned index,ExceptionCode & ec)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 replaceItemValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,PassListItemTearOff passNewItem,unsigned index,ExceptionCode & ec)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() canRemoveItem(PropertyType & values,unsigned index,ExceptionCode & ec)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 removeItemValues(PropertyType & values,unsigned index,ExceptionCode & ec)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 removeItemValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,unsigned index,ExceptionCode & ec)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() appendItemValues(PropertyType & values,const ListItemType & newItem,ExceptionCode & ec)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 appendItemValuesAndWrappers(AnimatedListPropertyTearOff * animatedList,PassListItemTearOff passNewItem,ExceptionCode & ec)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 role()409 virtual SVGPropertyRole role() const { return m_role; } 410 411 protected: SVGListProperty(SVGPropertyRole role)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