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