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