1 /* 2 * Copyright 2006-2012, 2023, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 * Zardshard 8 */ 9 10 #include "ShapeListView.h" 11 12 #include <new> 13 #include <stdio.h> 14 15 #include <Application.h> 16 #include <Catalog.h> 17 #include <ListItem.h> 18 #include <Locale.h> 19 #include <Menu.h> 20 #include <MenuItem.h> 21 #include <Message.h> 22 #include <Mime.h> 23 #include <Window.h> 24 25 #include "AddPathsCommand.h" 26 #include "AddShapesCommand.h" 27 #include "AddStylesCommand.h" 28 #include "CommandStack.h" 29 #include "CompoundCommand.h" 30 #include "FreezeTransformationCommand.h" 31 #include "MainWindow.h" 32 #include "MoveShapesCommand.h" 33 #include "Observer.h" 34 #include "ReferenceImage.h" 35 #include "PathContainer.h" 36 #include "PathSourceShape.h" 37 #include "RemoveShapesCommand.h" 38 #include "ResetTransformationCommand.h" 39 #include "Selection.h" 40 #include "Shape.h" 41 #include "Style.h" 42 #include "StyleContainer.h" 43 #include "Util.h" 44 #include "VectorPath.h" 45 46 47 #undef B_TRANSLATION_CONTEXT 48 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-ShapesList" 49 50 51 using std::nothrow; 52 53 class ShapeListItem : public SimpleItem, public Observer { 54 public: 55 ShapeListItem(Shape* s, ShapeListView* listView) 56 : 57 SimpleItem(""), 58 shape(NULL), 59 fListView(listView) 60 { 61 SetShape(s); 62 } 63 64 65 virtual ~ShapeListItem() 66 { 67 SetShape(NULL); 68 } 69 70 71 virtual void ObjectChanged(const Observable* object) 72 { 73 UpdateText(); 74 } 75 76 void SetShape(Shape* s) 77 { 78 if (s == shape) 79 return; 80 81 if (shape) { 82 shape->RemoveObserver(this); 83 shape->ReleaseReference(); 84 } 85 86 shape = s; 87 88 if (shape) { 89 shape->AcquireReference(); 90 shape->AddObserver(this); 91 UpdateText(); 92 } 93 } 94 95 void UpdateText() 96 { 97 SetText(shape->Name()); 98 if (fListView->LockLooper()) { 99 fListView->InvalidateItem(fListView->IndexOf(this)); 100 fListView->UnlockLooper(); 101 } 102 } 103 104 public: 105 Shape* shape; 106 107 private: 108 ShapeListView* fListView; 109 }; 110 111 112 // #pragma mark - 113 114 115 enum { 116 MSG_REMOVE = 'rmsh', 117 MSG_DUPLICATE = 'dpsh', 118 MSG_RESET_TRANSFORMATION = 'rstr', 119 MSG_FREEZE_TRANSFORMATION = 'frzt', 120 121 MSG_DRAG_SHAPE = 'drgs', 122 }; 123 124 125 ShapeListView::ShapeListView(BRect frame, const char* name, BMessage* message, 126 BHandler* target) 127 : 128 SimpleListView(frame, name, NULL, B_MULTIPLE_SELECTION_LIST), 129 fMessage(message), 130 fShapeContainer(NULL), 131 fStyleContainer(NULL), 132 fPathContainer(NULL), 133 fCommandStack(NULL) 134 { 135 SetDragCommand(MSG_DRAG_SHAPE); 136 SetTarget(target); 137 } 138 139 140 ShapeListView::~ShapeListView() 141 { 142 _MakeEmpty(); 143 delete fMessage; 144 145 if (fShapeContainer != NULL) 146 fShapeContainer->RemoveListener(this); 147 } 148 149 150 void 151 ShapeListView::SelectionChanged() 152 { 153 SimpleListView::SelectionChanged(); 154 155 if (!fSyncingToSelection) { 156 ShapeListItem* item 157 = dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(0))); 158 if (fMessage) { 159 BMessage message(*fMessage); 160 message.AddPointer("shape", item ? (void*)item->shape : NULL); 161 Invoke(&message); 162 } 163 } 164 165 _UpdateMenu(); 166 } 167 168 169 void 170 ShapeListView::MessageReceived(BMessage* message) 171 { 172 switch (message->what) { 173 case MSG_REMOVE: 174 RemoveSelected(); 175 break; 176 177 case MSG_DUPLICATE: 178 { 179 int32 count = CountSelectedItems(); 180 int32 index = 0; 181 BList items; 182 for (int32 i = 0; i < count; i++) { 183 index = CurrentSelection(i); 184 BListItem* item = ItemAt(index); 185 if (item) 186 items.AddItem((void*)item); 187 } 188 CopyItems(items, index + 1); 189 break; 190 } 191 192 case MSG_RESET_TRANSFORMATION: 193 { 194 BList shapes; 195 _GetSelectedShapes(shapes); 196 int32 count = shapes.CountItems(); 197 if (count < 0) 198 break; 199 200 Transformable* transformables[count]; 201 for (int32 i = 0; i < count; i++) { 202 Shape* shape = (Shape*)shapes.ItemAtFast(i); 203 transformables[i] = shape; 204 } 205 206 ResetTransformationCommand* command = 207 new ResetTransformationCommand(transformables, count); 208 209 fCommandStack->Perform(command); 210 break; 211 } 212 213 case MSG_FREEZE_TRANSFORMATION: 214 { 215 BList shapes; 216 _GetSelectedShapes(shapes); 217 int32 count = shapes.CountItems(); 218 if (count < 0) 219 break; 220 221 BList pathSourceShapes; 222 223 for (int i = 0; i < count; i++) { 224 Shape* shape = (Shape*) shapes.ItemAtFast(i); 225 if (dynamic_cast<PathSourceShape*>(shape) != NULL) 226 pathSourceShapes.AddItem(shape); 227 } 228 229 count = pathSourceShapes.CountItems(); 230 231 FreezeTransformationCommand* command 232 = new FreezeTransformationCommand( 233 (PathSourceShape**)pathSourceShapes.Items(), 234 count); 235 236 fCommandStack->Perform(command); 237 break; 238 } 239 240 default: 241 SimpleListView::MessageReceived(message); 242 break; 243 } 244 } 245 246 247 void 248 ShapeListView::MakeDragMessage(BMessage* message) const 249 { 250 SimpleListView::MakeDragMessage(message); 251 message->AddPointer("container", fShapeContainer); 252 int32 count = CountSelectedItems(); 253 for (int32 i = 0; i < count; i++) { 254 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 255 ItemAt(CurrentSelection(i))); 256 if (item != NULL && item->shape != NULL) { 257 PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(item->shape); 258 if (pathSourceShape != NULL) { 259 message->AddInt32("type", PathSourceShape::archive_code); 260 261 PathSourceShape* shape = pathSourceShape; 262 message->AddPointer("shape", (void*)shape); 263 264 // Add archives of everything this Shape uses 265 BMessage archive; 266 267 BMessage styleArchive; 268 shape->Style()->Archive(&styleArchive, true); 269 archive.AddMessage("style", &styleArchive); 270 271 PathContainer* paths = shape->Paths(); 272 for (int32 j = 0; j < paths->CountPaths(); j++) { 273 BMessage pathArchive; 274 paths->PathAt(j)->Archive(&pathArchive, true); 275 archive.AddMessage("path", &pathArchive); 276 } 277 278 BMessage shapeArchive; 279 shape->Archive(&shapeArchive, true); 280 archive.AddMessage("shape", &shapeArchive); 281 282 message->AddMessage("shape archive", &archive); 283 continue; 284 } 285 286 ReferenceImage* referenceImage = dynamic_cast<ReferenceImage*>(item->shape); 287 if (referenceImage != NULL) { 288 message->AddInt32("type", ReferenceImage::archive_code); 289 290 message->AddPointer("shape", (void*)referenceImage); 291 292 // Add archives of everything this Shape uses 293 BMessage archive; 294 295 BMessage shapeArchive; 296 referenceImage->Archive(&shapeArchive, true); 297 archive.AddMessage("shape", &shapeArchive); 298 299 message->AddMessage("shape archive", &archive); 300 continue; 301 } 302 } else 303 break; 304 } 305 } 306 307 308 bool 309 ShapeListView::AcceptDragMessage(const BMessage* message) const 310 { 311 return SimpleListView::AcceptDragMessage(message); 312 } 313 314 315 void 316 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where) 317 { 318 SimpleListView::SetDropTargetRect(message, where); 319 } 320 321 322 bool 323 ShapeListView::HandleDropMessage(const BMessage* message, int32 dropIndex) 324 { 325 // Let SimpleListView handle drag-sorting (when drag came from ourself) 326 if (SimpleListView::HandleDropMessage(message, dropIndex)) 327 return true; 328 329 if (fCommandStack == NULL || fShapeContainer == NULL 330 || fStyleContainer == NULL || fPathContainer == NULL) { 331 return false; 332 } 333 334 // Drag may have come from another instance, like in another window. 335 // Reconstruct the Shapes from the archive and add them at the drop 336 // index. 337 int index = 0; 338 BList styles; 339 BList paths; 340 BList shapes; 341 while (true) { 342 int32 type; 343 if (message->FindInt32("type", index, &type) != B_OK) 344 break; 345 346 if (type == PathSourceShape::archive_code) { 347 BMessage archive; 348 if (message->FindMessage("shape archive", index, &archive) != B_OK) 349 break; 350 351 // Extract the shape archive 352 BMessage shapeArchive; 353 if (archive.FindMessage("shape", &shapeArchive) != B_OK) 354 break; 355 356 // Extract the style 357 BMessage styleArchive; 358 if (archive.FindMessage("style", &styleArchive) != B_OK) 359 break; 360 361 Style* style = new Style(&styleArchive); 362 if (style == NULL) 363 break; 364 365 Style* styleToAssign = style; 366 // Try to find an existing style that is the same as the extracted 367 // style and use that one instead. 368 for (int32 i = 0; i < fStyleContainer->CountStyles(); i++) { 369 Style* other = fStyleContainer->StyleAtFast(i); 370 if (*other == *style) { 371 styleToAssign = other; 372 delete style; 373 style = NULL; 374 break; 375 } 376 } 377 378 if (style != NULL && !styles.AddItem(style)) { 379 delete style; 380 break; 381 } 382 383 // Create the shape using the given style 384 PathSourceShape* shape = new(std::nothrow) PathSourceShape(styleToAssign); 385 if (shape == NULL) 386 break; 387 388 if (shape->Unarchive(&shapeArchive) != B_OK 389 || !shapes.AddItem(shape)) { 390 delete shape; 391 if (style != NULL) { 392 styles.RemoveItem(style); 393 delete style; 394 } 395 break; 396 } 397 398 // Extract the paths 399 int pathIndex = 0; 400 while (true) { 401 BMessage pathArchive; 402 if (archive.FindMessage("path", pathIndex, &pathArchive) != B_OK) 403 break; 404 405 VectorPath* path = new(nothrow) VectorPath(&pathArchive); 406 if (path == NULL) 407 break; 408 409 VectorPath* pathToInclude = path; 410 for (int32 i = 0; i < fPathContainer->CountPaths(); i++) { 411 VectorPath* other = fPathContainer->PathAtFast(i); 412 if (*other == *path) { 413 pathToInclude = other; 414 delete path; 415 path = NULL; 416 break; 417 } 418 } 419 420 if (path != NULL && !paths.AddItem(path)) { 421 delete path; 422 break; 423 } 424 425 shape->Paths()->AddPath(pathToInclude); 426 427 pathIndex++; 428 } 429 } else if (type == ReferenceImage::archive_code) { 430 BMessage archive; 431 if (message->FindMessage("shape archive", index, &archive) != B_OK) 432 break; 433 434 BMessage shapeArchive; 435 if (archive.FindMessage("shape", &shapeArchive) != B_OK) 436 break; 437 438 ReferenceImage* shape = new (std::nothrow) ReferenceImage(&shapeArchive); 439 if (shape == NULL) 440 break; 441 442 if (shapes.AddItem(shape) != B_OK) 443 break; 444 } 445 446 index++; 447 } 448 449 int32 shapeCount = shapes.CountItems(); 450 if (shapeCount == 0) 451 return false; 452 453 // TODO: Add allocation checks beyond this point. 454 455 AddStylesCommand* stylesCommand = new(std::nothrow) AddStylesCommand( 456 fStyleContainer, (Style**)styles.Items(), styles.CountItems(), 457 fStyleContainer->CountStyles()); 458 459 AddPathsCommand* pathsCommand = new(std::nothrow) AddPathsCommand( 460 fPathContainer, (VectorPath**)paths.Items(), paths.CountItems(), 461 true, fPathContainer->CountPaths()); 462 463 AddShapesCommand* shapesCommand = new(std::nothrow) AddShapesCommand( 464 fShapeContainer, (Shape**)shapes.Items(), shapeCount, dropIndex, 465 fSelection); 466 467 ::Command** commands = new(std::nothrow) ::Command*[3]; 468 469 commands[0] = stylesCommand; 470 commands[1] = pathsCommand; 471 commands[2] = shapesCommand; 472 473 CompoundCommand* command = new CompoundCommand(commands, 3, 474 B_TRANSLATE("Drop shapes"), -1); 475 476 fCommandStack->Perform(command); 477 478 return true; 479 } 480 481 482 // #pragma mark - 483 484 485 void 486 ShapeListView::MoveItems(BList& items, int32 toIndex) 487 { 488 if (fCommandStack == NULL || fShapeContainer == NULL) 489 return; 490 491 int32 count = items.CountItems(); 492 Shape** shapes = new(nothrow) Shape*[count]; 493 if (shapes == NULL) 494 return; 495 496 for (int32 i = 0; i < count; i++) { 497 ShapeListItem* item 498 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 499 shapes[i] = item ? item->shape : NULL; 500 } 501 502 MoveShapesCommand* command = new (nothrow) MoveShapesCommand( 503 fShapeContainer, shapes, count, toIndex); 504 if (command == NULL) { 505 delete[] shapes; 506 return; 507 } 508 509 fCommandStack->Perform(command); 510 } 511 512 // CopyItems 513 void 514 ShapeListView::CopyItems(BList& items, int32 toIndex) 515 { 516 if (fCommandStack == NULL || fShapeContainer == NULL) 517 return; 518 519 int32 count = items.CountItems(); 520 Shape* shapes[count]; 521 522 for (int32 i = 0; i < count; i++) { 523 ShapeListItem* item 524 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 525 shapes[i] = item ? item->shape->Clone() : NULL; 526 } 527 528 AddShapesCommand* command = new(nothrow) AddShapesCommand(fShapeContainer, 529 shapes, count, toIndex, fSelection); 530 if (command == NULL) { 531 for (int32 i = 0; i < count; i++) 532 delete shapes[i]; 533 return; 534 } 535 536 fCommandStack->Perform(command); 537 } 538 539 540 void 541 ShapeListView::RemoveItemList(BList& items) 542 { 543 if (fCommandStack == NULL || fShapeContainer == NULL) 544 return; 545 546 int32 count = items.CountItems(); 547 int32 indices[count]; 548 for (int32 i = 0; i < count; i++) 549 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); 550 551 RemoveShapesCommand* command = new(nothrow) RemoveShapesCommand( 552 fShapeContainer, indices, count); 553 554 fCommandStack->Perform(command); 555 } 556 557 558 BListItem* 559 ShapeListView::CloneItem(int32 index) const 560 { 561 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index)); 562 if (item != NULL) { 563 return new ShapeListItem(item->shape, 564 const_cast<ShapeListView*>(this)); 565 } 566 return NULL; 567 } 568 569 570 int32 571 ShapeListView::IndexOfSelectable(Selectable* selectable) const 572 { 573 Shape* shape = dynamic_cast<Shape*>(selectable); 574 if (shape == NULL) { 575 Transformer* transformer = dynamic_cast<Transformer*>(selectable); 576 if (transformer == NULL) 577 return -1; 578 int32 count = CountItems(); 579 for (int32 i = 0; i < count; i++) { 580 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 581 if (item != NULL && item->shape->HasTransformer(transformer)) 582 return i; 583 } 584 } else { 585 int32 count = CountItems(); 586 for (int32 i = 0; i < count; i++) { 587 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 588 if (item != NULL && item->shape == shape) 589 return i; 590 } 591 } 592 593 return -1; 594 } 595 596 597 Selectable* 598 ShapeListView::SelectableFor(BListItem* item) const 599 { 600 ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item); 601 if (shapeItem != NULL) 602 return shapeItem->shape; 603 return NULL; 604 } 605 606 607 // #pragma mark - 608 609 610 void 611 ShapeListView::ShapeAdded(Shape* shape, int32 index) 612 { 613 // NOTE: we are in the thread that messed with the 614 // ShapeContainer, so no need to lock the 615 // container, when this is changed to asynchronous 616 // notifications, then it would need to be read-locked! 617 if (!LockLooper()) 618 return; 619 620 if (_AddShape(shape, index)) 621 Select(index); 622 623 UnlockLooper(); 624 } 625 626 627 void 628 ShapeListView::ShapeRemoved(Shape* shape) 629 { 630 // NOTE: we are in the thread that messed with the 631 // ShapeContainer, so no need to lock the 632 // container, when this is changed to asynchronous 633 // notifications, then it would need to be read-locked! 634 if (!LockLooper()) 635 return; 636 637 // NOTE: we're only interested in Shape objects 638 _RemoveShape(shape); 639 640 UnlockLooper(); 641 } 642 643 644 // #pragma mark - 645 646 647 void 648 ShapeListView::SetMenu(BMenu* menu) 649 { 650 if (fMenu == menu) 651 return; 652 653 fMenu = menu; 654 655 if (fMenu == NULL) 656 return; 657 658 BMessage* message = new BMessage(MSG_ADD_SHAPE); 659 fAddEmptyMI = new BMenuItem(B_TRANSLATE("Add empty"), message); 660 661 message = new BMessage(MSG_ADD_SHAPE); 662 message->AddBool("path", true); 663 fAddWidthPathMI = new BMenuItem(B_TRANSLATE("Add with path"), message); 664 665 message = new BMessage(MSG_ADD_SHAPE); 666 message->AddBool("style", true); 667 fAddWidthStyleMI = new BMenuItem(B_TRANSLATE("Add with style"), message); 668 669 message = new BMessage(MSG_ADD_SHAPE); 670 message->AddBool("path", true); 671 message->AddBool("style", true); 672 fAddWidthPathAndStyleMI = new BMenuItem( 673 B_TRANSLATE("Add with path & style"), message); 674 675 message = new BMessage(MSG_OPEN); 676 message->AddBool("reference image", true); 677 fAddReferenceImageMI = new BMenuItem(B_TRANSLATE("Add reference image"), message); 678 679 fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"), 680 new BMessage(MSG_DUPLICATE)); 681 fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"), 682 new BMessage(MSG_RESET_TRANSFORMATION)); 683 fFreezeTransformationMI = new BMenuItem( 684 B_TRANSLATE("Freeze transformation"), 685 new BMessage(MSG_FREEZE_TRANSFORMATION)); 686 687 fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); 688 689 690 fMenu->AddItem(fAddEmptyMI); 691 fMenu->AddItem(fAddWidthPathMI); 692 fMenu->AddItem(fAddWidthStyleMI); 693 fMenu->AddItem(fAddWidthPathAndStyleMI); 694 695 fMenu->AddSeparatorItem(); 696 697 fMenu->AddItem(fAddReferenceImageMI); 698 699 fMenu->AddSeparatorItem(); 700 701 fMenu->AddItem(fDuplicateMI); 702 fMenu->AddItem(fResetTransformationMI); 703 fMenu->AddItem(fFreezeTransformationMI); 704 fMenu->AddSeparatorItem(); 705 706 fMenu->AddItem(fRemoveMI); 707 708 fDuplicateMI->SetTarget(this); 709 fResetTransformationMI->SetTarget(this); 710 fFreezeTransformationMI->SetTarget(this); 711 fRemoveMI->SetTarget(this); 712 713 _UpdateMenu(); 714 } 715 716 717 void 718 ShapeListView::SetShapeContainer(ShapeContainer* container) 719 { 720 if (fShapeContainer == container) 721 return; 722 723 // detach from old container 724 if (fShapeContainer != NULL) 725 fShapeContainer->RemoveListener(this); 726 727 _MakeEmpty(); 728 729 fShapeContainer = container; 730 731 if (fShapeContainer == NULL) 732 return; 733 734 fShapeContainer->AddListener(this); 735 736 // sync 737 int32 count = fShapeContainer->CountShapes(); 738 for (int32 i = 0; i < count; i++) 739 _AddShape(fShapeContainer->ShapeAtFast(i), i); 740 } 741 742 743 void 744 ShapeListView::SetStyleContainer(StyleContainer* container) 745 { 746 fStyleContainer = container; 747 } 748 749 750 void 751 ShapeListView::SetPathContainer(PathContainer* container) 752 { 753 fPathContainer = container; 754 } 755 756 757 void 758 ShapeListView::SetCommandStack(CommandStack* stack) 759 { 760 fCommandStack = stack; 761 } 762 763 764 // #pragma mark - 765 766 767 bool 768 ShapeListView::_AddShape(Shape* shape, int32 index) 769 { 770 if (shape == NULL) 771 return false; 772 773 ShapeListItem* item = new(std::nothrow) ShapeListItem(shape, this); 774 if (item == NULL) 775 return false; 776 777 if (!AddItem(item, index)) { 778 delete item; 779 return false; 780 } 781 782 return true; 783 } 784 785 786 bool 787 ShapeListView::_RemoveShape(Shape* shape) 788 { 789 ShapeListItem* item = _ItemForShape(shape); 790 if (item != NULL && RemoveItem(item)) { 791 delete item; 792 return true; 793 } 794 return false; 795 } 796 797 798 ShapeListItem* 799 ShapeListView::_ItemForShape(Shape* shape) const 800 { 801 int32 count = CountItems(); 802 for (int32 i = 0; i < count; i++) { 803 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 804 if (item != NULL && item->shape == shape) 805 return item; 806 } 807 return NULL; 808 } 809 810 811 void 812 ShapeListView::_UpdateMenu() 813 { 814 if (fMenu == NULL) 815 return; 816 817 bool gotSelection = CurrentSelection(0) >= 0; 818 819 fDuplicateMI->SetEnabled(gotSelection); 820 fResetTransformationMI->SetEnabled(gotSelection); 821 fFreezeTransformationMI->SetEnabled(gotSelection); 822 fRemoveMI->SetEnabled(gotSelection); 823 824 if (gotSelection) { 825 bool hasPathSourceShape = false; 826 827 int32 count = CountSelectedItems(); 828 for (int32 i = 0; i < count; i++) { 829 ShapeListItem* item 830 = dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(i))); 831 bool isPathSourceShape 832 = item ? dynamic_cast<PathSourceShape*>(item->shape) != NULL : false; 833 hasPathSourceShape |= isPathSourceShape; 834 } 835 836 fFreezeTransformationMI->SetEnabled(hasPathSourceShape); 837 } 838 } 839 840 841 void 842 ShapeListView::_GetSelectedShapes(BList& shapes) const 843 { 844 int32 count = CountSelectedItems(); 845 for (int32 i = 0; i < count; i++) { 846 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 847 ItemAt(CurrentSelection(i))); 848 if (item != NULL && item->shape != NULL) { 849 if (!shapes.AddItem((void*)item->shape)) 850 break; 851 } 852 } 853 } 854