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