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