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) 255 return B_ERROR; 256 257 PathSourceShape* shape = dynamic_cast<PathSourceShape*>(item->shape); 258 if (shape != NULL) { 259 BMessage archive; 260 archive.what = PathSourceShape::archive_code; 261 262 BMessage styleArchive; 263 if (shape->Style() != NULL) { 264 shape->Style()->Archive(&styleArchive, true); 265 archive.AddMessage("style", &styleArchive); 266 } 267 268 if (shape->Paths() != NULL) { 269 int32 pathsCount = shape->Paths()->CountItems(); 270 for (int32 j = 0; j < pathsCount; j++) { 271 BMessage pathArchive; 272 if (shape->Paths()->ItemAt(j) != NULL) { 273 shape->Paths()->ItemAt(j)->Archive(&pathArchive, true); 274 archive.AddMessage("path", &pathArchive); 275 } 276 } 277 } 278 279 BMessage shapeArchive; 280 shape->Archive(&shapeArchive, true); 281 archive.AddMessage("shape", &shapeArchive); 282 283 into->AddMessage("shape archive", &archive); 284 continue; 285 } 286 287 ReferenceImage* referenceImage = dynamic_cast<ReferenceImage*>(item->shape); 288 if (referenceImage != NULL) { 289 BMessage archive; 290 archive.what = ReferenceImage::archive_code; 291 292 BMessage shapeArchive; 293 referenceImage->Archive(&shapeArchive, true); 294 archive.AddMessage("shape", &shapeArchive); 295 296 into->AddMessage("shape archive", &archive); 297 continue; 298 } 299 } 300 301 return B_OK; 302 } 303 304 305 bool 306 ShapeListView::InstantiateSelection(const BMessage* archive, int32 dropIndex) 307 { 308 if (archive->what != ShapeListView::kSelectionArchiveCode 309 || fCommandStack == NULL || fShapeContainer == NULL 310 || fStyleContainer == NULL || fPathContainer == NULL) { 311 return false; 312 } 313 314 // Drag may have come from another instance, like in another window. 315 // Reconstruct the Shapes from the archive and add them at the drop 316 // index. 317 int index = 0; 318 BList styles; 319 BList paths; 320 BList shapes; 321 while (true) { 322 BMessage shapeArchive; 323 if (archive->FindMessage("shape archive", index, &shapeArchive) != B_OK) 324 break; 325 326 if (shapeArchive.what == PathSourceShape::archive_code) { 327 // Extract the style 328 BMessage styleArchive; 329 if (shapeArchive.FindMessage("style", &styleArchive) != B_OK) 330 break; 331 332 Style* style = new Style(&styleArchive); 333 if (style == NULL) 334 break; 335 336 Style* styleToAssign = style; 337 // Try to find an existing style that is the same as the extracted 338 // style and use that one instead. 339 for (int32 i = 0; i < fStyleContainer->CountItems(); i++) { 340 Style* other = fStyleContainer->ItemAtFast(i); 341 if (*other == *style) { 342 styleToAssign = other; 343 delete style; 344 style = NULL; 345 break; 346 } 347 } 348 349 if (style != NULL && !styles.AddItem(style)) { 350 delete style; 351 break; 352 } 353 354 // Create the shape using the given style 355 PathSourceShape* shape = new(std::nothrow) PathSourceShape(styleToAssign); 356 if (shape == NULL) 357 break; 358 359 // Extract the shape archive 360 BMessage shapeMessage; 361 if (shapeArchive.FindMessage("shape", &shapeMessage) != B_OK) 362 break; 363 if (shape->Unarchive(&shapeMessage) != B_OK 364 || !shapes.AddItem(shape)) { 365 delete shape; 366 if (style != NULL) { 367 styles.RemoveItem(style); 368 delete style; 369 } 370 break; 371 } 372 373 // Extract the paths 374 int pathIndex = 0; 375 while (true) { 376 BMessage pathArchive; 377 if (shapeArchive.FindMessage("path", pathIndex, &pathArchive) != B_OK) 378 break; 379 380 VectorPath* path = new(nothrow) VectorPath(&pathArchive); 381 if (path == NULL) 382 break; 383 384 VectorPath* pathToInclude = path; 385 for (int32 i = 0; i < fPathContainer->CountItems(); i++) { 386 VectorPath* other = fPathContainer->ItemAtFast(i); 387 if (*other == *path) { 388 pathToInclude = other; 389 delete path; 390 path = NULL; 391 break; 392 } 393 } 394 395 if (path != NULL && !paths.AddItem(path)) { 396 delete path; 397 break; 398 } 399 400 shape->Paths()->AddItem(pathToInclude); 401 402 pathIndex++; 403 } 404 } else if (shapeArchive.what == ReferenceImage::archive_code) { 405 BMessage shapeMessage; 406 if (shapeArchive.FindMessage("shape", &shapeMessage) != B_OK) 407 break; 408 409 ReferenceImage* shape = new (std::nothrow) ReferenceImage(&shapeMessage); 410 if (shape == NULL) 411 break; 412 413 if (shapes.AddItem(shape) != B_OK) 414 break; 415 } 416 417 index++; 418 } 419 420 int32 shapeCount = shapes.CountItems(); 421 if (shapeCount == 0) 422 return false; 423 424 // TODO: Add allocation checks beyond this point. 425 426 AddStylesCommand* stylesCommand = new(std::nothrow) AddStylesCommand( 427 fStyleContainer, (Style**)styles.Items(), styles.CountItems(), 428 fStyleContainer->CountItems()); 429 430 AddPathsCommand* pathsCommand = new(std::nothrow) AddPathsCommand( 431 fPathContainer, (VectorPath**)paths.Items(), paths.CountItems(), 432 true, fPathContainer->CountItems()); 433 434 AddShapesCommand* shapesCommand = new(std::nothrow) AddShapesCommand( 435 fShapeContainer, (Shape**)shapes.Items(), shapeCount, dropIndex); 436 437 ::Command** commands = new(std::nothrow) ::Command*[3]; 438 439 commands[0] = stylesCommand; 440 commands[1] = pathsCommand; 441 commands[2] = shapesCommand; 442 443 CompoundCommand* command = new CompoundCommand(commands, 3, 444 B_TRANSLATE("Drop shapes"), -1); 445 446 fCommandStack->Perform(command); 447 448 return true; 449 } 450 451 452 // #pragma mark - 453 454 455 void 456 ShapeListView::MoveItems(BList& items, int32 toIndex) 457 { 458 if (fCommandStack == NULL || fShapeContainer == NULL) 459 return; 460 461 int32 count = items.CountItems(); 462 Shape** shapes = new(nothrow) Shape*[count]; 463 if (shapes == NULL) 464 return; 465 466 for (int32 i = 0; i < count; i++) { 467 ShapeListItem* item 468 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 469 shapes[i] = item ? item->shape : NULL; 470 } 471 472 MoveShapesCommand* command = new (nothrow) MoveShapesCommand( 473 fShapeContainer, shapes, count, toIndex); 474 if (command == NULL) { 475 delete[] shapes; 476 return; 477 } 478 479 fCommandStack->Perform(command); 480 } 481 482 // CopyItems 483 void 484 ShapeListView::CopyItems(BList& items, int32 toIndex) 485 { 486 if (fCommandStack == NULL || fShapeContainer == NULL) 487 return; 488 489 int32 count = items.CountItems(); 490 Shape* shapes[count]; 491 492 for (int32 i = 0; i < count; i++) { 493 ShapeListItem* item 494 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 495 shapes[i] = item ? item->shape->Clone() : NULL; 496 } 497 498 AddShapesCommand* command = new(nothrow) AddShapesCommand(fShapeContainer, 499 shapes, count, toIndex); 500 if (command == NULL) { 501 for (int32 i = 0; i < count; i++) 502 delete shapes[i]; 503 return; 504 } 505 506 fCommandStack->Perform(command); 507 } 508 509 510 void 511 ShapeListView::RemoveItemList(BList& items) 512 { 513 if (fCommandStack == NULL || fShapeContainer == NULL) 514 return; 515 516 int32 count = items.CountItems(); 517 int32 indices[count]; 518 for (int32 i = 0; i < count; i++) 519 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); 520 521 RemoveShapesCommand* command = new(nothrow) RemoveShapesCommand( 522 fShapeContainer, indices, count); 523 524 fCommandStack->Perform(command); 525 } 526 527 528 BListItem* 529 ShapeListView::CloneItem(int32 index) const 530 { 531 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index)); 532 if (item != NULL) { 533 return new ShapeListItem(item->shape, 534 const_cast<ShapeListView*>(this)); 535 } 536 return NULL; 537 } 538 539 540 int32 541 ShapeListView::IndexOfSelectable(Selectable* selectable) const 542 { 543 Shape* shape = dynamic_cast<Shape*>(selectable); 544 if (shape == NULL) { 545 Transformer* transformer = dynamic_cast<Transformer*>(selectable); 546 if (transformer == NULL) 547 return -1; 548 int32 count = CountItems(); 549 for (int32 i = 0; i < count; i++) { 550 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 551 if (item != NULL && item->shape->Transformers()->HasItem(transformer)) 552 return i; 553 } 554 } else { 555 int32 count = CountItems(); 556 for (int32 i = 0; i < count; i++) { 557 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 558 if (item != NULL && item->shape == shape) 559 return i; 560 } 561 } 562 563 return -1; 564 } 565 566 567 Selectable* 568 ShapeListView::SelectableFor(BListItem* item) const 569 { 570 ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item); 571 if (shapeItem != NULL) 572 return shapeItem->shape; 573 return NULL; 574 } 575 576 577 // #pragma mark - 578 579 580 void 581 ShapeListView::ItemAdded(Shape* shape, int32 index) 582 { 583 // NOTE: we are in the thread that messed with the 584 // ShapeContainer, so no need to lock the 585 // container, when this is changed to asynchronous 586 // notifications, then it would need to be read-locked! 587 if (!LockLooper()) 588 return; 589 590 if (_AddShape(shape, index)) 591 Select(index); 592 593 UnlockLooper(); 594 } 595 596 597 void 598 ShapeListView::ItemRemoved(Shape* shape) 599 { 600 // NOTE: we are in the thread that messed with the 601 // ShapeContainer, so no need to lock the 602 // container, when this is changed to asynchronous 603 // notifications, then it would need to be read-locked! 604 if (!LockLooper()) 605 return; 606 607 // NOTE: we're only interested in Shape objects 608 _RemoveShape(shape); 609 610 UnlockLooper(); 611 } 612 613 614 // #pragma mark - 615 616 617 void 618 ShapeListView::SetMenu(BMenu* menu) 619 { 620 if (fMenu == menu) 621 return; 622 623 fMenu = menu; 624 625 if (fMenu == NULL) 626 return; 627 628 BMessage* message = new BMessage(MSG_ADD_SHAPE); 629 fAddEmptyMI = new BMenuItem(B_TRANSLATE("Add empty"), message); 630 631 message = new BMessage(MSG_ADD_SHAPE); 632 message->AddBool("path", true); 633 fAddWidthPathMI = new BMenuItem(B_TRANSLATE("Add with path"), message); 634 635 message = new BMessage(MSG_ADD_SHAPE); 636 message->AddBool("style", true); 637 fAddWidthStyleMI = new BMenuItem(B_TRANSLATE("Add with style"), message); 638 639 message = new BMessage(MSG_ADD_SHAPE); 640 message->AddBool("path", true); 641 message->AddBool("style", true); 642 fAddWidthPathAndStyleMI = new BMenuItem( 643 B_TRANSLATE("Add with path & style"), message); 644 645 message = new BMessage(MSG_OPEN); 646 message->AddBool("reference image", true); 647 fAddReferenceImageMI = new BMenuItem(B_TRANSLATE("Add reference image"), message); 648 649 fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"), 650 new BMessage(MSG_DUPLICATE)); 651 fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"), 652 new BMessage(MSG_RESET_TRANSFORMATION)); 653 fFreezeTransformationMI = new BMenuItem( 654 B_TRANSLATE("Freeze transformation"), 655 new BMessage(MSG_FREEZE_TRANSFORMATION)); 656 657 fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); 658 659 660 fMenu->AddItem(fAddEmptyMI); 661 fMenu->AddItem(fAddWidthPathMI); 662 fMenu->AddItem(fAddWidthStyleMI); 663 fMenu->AddItem(fAddWidthPathAndStyleMI); 664 665 fMenu->AddSeparatorItem(); 666 667 fMenu->AddItem(fAddReferenceImageMI); 668 669 fMenu->AddSeparatorItem(); 670 671 fMenu->AddItem(fDuplicateMI); 672 fMenu->AddItem(fResetTransformationMI); 673 fMenu->AddItem(fFreezeTransformationMI); 674 fMenu->AddSeparatorItem(); 675 676 fMenu->AddItem(fRemoveMI); 677 678 fDuplicateMI->SetTarget(this); 679 fResetTransformationMI->SetTarget(this); 680 fFreezeTransformationMI->SetTarget(this); 681 fRemoveMI->SetTarget(this); 682 683 _UpdateMenu(); 684 } 685 686 687 void 688 ShapeListView::SetShapeContainer(Container<Shape>* container) 689 { 690 if (fShapeContainer == container) 691 return; 692 693 // detach from old container 694 if (fShapeContainer != NULL) 695 fShapeContainer->RemoveListener(this); 696 697 _MakeEmpty(); 698 699 fShapeContainer = container; 700 701 if (fShapeContainer == NULL) 702 return; 703 704 fShapeContainer->AddListener(this); 705 706 // sync 707 int32 count = fShapeContainer->CountItems(); 708 for (int32 i = 0; i < count; i++) 709 _AddShape(fShapeContainer->ItemAtFast(i), i); 710 } 711 712 713 void 714 ShapeListView::SetStyleContainer(Container<Style>* container) 715 { 716 fStyleContainer = container; 717 } 718 719 720 void 721 ShapeListView::SetPathContainer(Container<VectorPath>* container) 722 { 723 fPathContainer = container; 724 } 725 726 727 void 728 ShapeListView::SetCommandStack(CommandStack* stack) 729 { 730 fCommandStack = stack; 731 } 732 733 734 // #pragma mark - 735 736 737 bool 738 ShapeListView::_AddShape(Shape* shape, int32 index) 739 { 740 if (shape == NULL) 741 return false; 742 743 ShapeListItem* item = new(std::nothrow) ShapeListItem(shape, this); 744 if (item == NULL) 745 return false; 746 747 if (!AddItem(item, index)) { 748 delete item; 749 return false; 750 } 751 752 return true; 753 } 754 755 756 bool 757 ShapeListView::_RemoveShape(Shape* shape) 758 { 759 ShapeListItem* item = _ItemForShape(shape); 760 if (item != NULL && RemoveItem(item)) { 761 delete item; 762 return true; 763 } 764 return false; 765 } 766 767 768 ShapeListItem* 769 ShapeListView::_ItemForShape(Shape* shape) const 770 { 771 int32 count = CountItems(); 772 for (int32 i = 0; i < count; i++) { 773 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 774 if (item != NULL && item->shape == shape) 775 return item; 776 } 777 return NULL; 778 } 779 780 781 void 782 ShapeListView::_UpdateMenu() 783 { 784 if (fMenu == NULL) 785 return; 786 787 bool gotSelection = CurrentSelection(0) >= 0; 788 789 fDuplicateMI->SetEnabled(gotSelection); 790 fResetTransformationMI->SetEnabled(gotSelection); 791 fFreezeTransformationMI->SetEnabled(gotSelection); 792 fRemoveMI->SetEnabled(gotSelection); 793 794 if (gotSelection) { 795 bool hasPathSourceShape = false; 796 797 int32 count = CountSelectedItems(); 798 for (int32 i = 0; i < count; i++) { 799 ShapeListItem* item 800 = dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(i))); 801 bool isPathSourceShape 802 = item ? dynamic_cast<PathSourceShape*>(item->shape) != NULL : false; 803 hasPathSourceShape |= isPathSourceShape; 804 } 805 806 fFreezeTransformationMI->SetEnabled(hasPathSourceShape); 807 } 808 } 809 810 811 void 812 ShapeListView::_GetSelectedShapes(BList& shapes) const 813 { 814 int32 count = CountSelectedItems(); 815 for (int32 i = 0; i < count; i++) { 816 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 817 ItemAt(CurrentSelection(i))); 818 if (item != NULL && item->shape != NULL) { 819 if (!shapes.AddItem((void*)item->shape)) 820 break; 821 } 822 } 823 } 824