1 /* 2 * Copyright 2006, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "PathListView.h" 10 11 #include <stdio.h> 12 13 #include <Application.h> 14 #include <ListItem.h> 15 #include <Menu.h> 16 #include <MenuItem.h> 17 #include <Message.h> 18 #include <Mime.h> 19 #include <Window.h> 20 21 #include "AddPathsCommand.h" 22 #include "CleanUpPathCommand.h" 23 #include "CommandStack.h" 24 #include "MovePathsCommand.h" 25 #include "Observer.h" 26 #include "RemovePathsCommand.h" 27 #include "ReversePathCommand.h" 28 #include "Shape.h" 29 #include "ShapeContainer.h" 30 #include "Selection.h" 31 #include "UnassignPathCommand.h" 32 #include "Util.h" 33 #include "VectorPath.h" 34 35 using std::nothrow; 36 37 static const float kMarkWidth = 14.0; 38 static const float kBorderOffset = 3.0; 39 static const float kTextOffset = 4.0; 40 41 class PathListItem : public SimpleItem, 42 public Observer { 43 public: 44 PathListItem(VectorPath* p, 45 PathListView* listView, 46 bool markEnabled) 47 : SimpleItem(""), 48 path(NULL), 49 fListView(listView), 50 fMarkEnabled(markEnabled), 51 fMarked(false) 52 { 53 SetPath(p); 54 } 55 56 virtual ~PathListItem() 57 { 58 SetPath(NULL); 59 } 60 61 // SimpleItem interface 62 virtual void Draw(BView* owner, BRect itemFrame, uint32 flags) 63 { 64 SimpleItem::DrawBackground(owner, itemFrame, flags); 65 66 // text 67 owner->SetHighColor(0, 0, 0, 255); 68 font_height fh; 69 owner->GetFontHeight(&fh); 70 BString truncatedString(Text()); 71 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 72 itemFrame.Width() 73 - kBorderOffset 74 - kMarkWidth 75 - kTextOffset 76 - kBorderOffset); 77 float height = itemFrame.Height(); 78 float textHeight = fh.ascent + fh.descent; 79 BPoint pos; 80 pos.x = itemFrame.left 81 + kBorderOffset + kMarkWidth + kTextOffset; 82 pos.y = itemFrame.top 83 + ceilf((height - textHeight) / 2.0 + fh.ascent); 84 owner->DrawString(truncatedString.String(), pos); 85 86 if (!fMarkEnabled) 87 return; 88 89 // mark 90 BRect markRect = itemFrame; 91 markRect.left += kBorderOffset; 92 markRect.right = markRect.left + kMarkWidth; 93 markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0; 94 markRect.bottom = markRect.top + kMarkWidth; 95 owner->SetHighColor(tint_color(owner->LowColor(), B_DARKEN_1_TINT)); 96 owner->StrokeRect(markRect); 97 markRect.InsetBy(1, 1); 98 owner->SetHighColor(tint_color(owner->LowColor(), 1.04)); 99 owner->FillRect(markRect); 100 if (fMarked) { 101 markRect.InsetBy(2, 2); 102 owner->SetHighColor(tint_color(owner->LowColor(), 103 B_DARKEN_4_TINT)); 104 owner->SetPenSize(2); 105 owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom()); 106 owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop()); 107 owner->SetPenSize(1); 108 } 109 } 110 111 // Observer interface 112 virtual void ObjectChanged(const Observable* object) 113 { 114 UpdateText(); 115 } 116 117 // PathListItem 118 void SetPath(VectorPath* p) 119 { 120 if (p == path) 121 return; 122 123 if (path) { 124 path->RemoveObserver(this); 125 path->Release(); 126 } 127 128 path = p; 129 130 if (path) { 131 path->Acquire(); 132 path->AddObserver(this); 133 UpdateText(); 134 } 135 } 136 void UpdateText() 137 { 138 SetText(path->Name()); 139 Invalidate(); 140 } 141 142 void SetMarkEnabled(bool enabled) 143 { 144 if (fMarkEnabled == enabled) 145 return; 146 fMarkEnabled = enabled; 147 Invalidate(); 148 } 149 void SetMarked(bool marked) 150 { 151 if (fMarked == marked) 152 return; 153 fMarked = marked; 154 Invalidate(); 155 } 156 157 void Invalidate() 158 { 159 // :-/ 160 if (fListView->LockLooper()) { 161 fListView->InvalidateItem( 162 fListView->IndexOf(this)); 163 fListView->UnlockLooper(); 164 } 165 } 166 167 VectorPath* path; 168 private: 169 PathListView* fListView; 170 bool fMarkEnabled; 171 bool fMarked; 172 }; 173 174 175 class ShapePathListener : public PathContainerListener, 176 public ShapeContainerListener { 177 public: 178 ShapePathListener(PathListView* listView) 179 : fListView(listView), 180 fShape(NULL) 181 { 182 } 183 virtual ~ShapePathListener() 184 { 185 SetShape(NULL); 186 } 187 188 // PathContainerListener interface 189 virtual void PathAdded(VectorPath* path, int32 index) 190 { 191 fListView->_SetPathMarked(path, true); 192 } 193 virtual void PathRemoved(VectorPath* path) 194 { 195 fListView->_SetPathMarked(path, false); 196 } 197 198 // ShapeContainerListener interface 199 virtual void ShapeAdded(Shape* shape, int32 index) {} 200 virtual void ShapeRemoved(Shape* shape) 201 { 202 fListView->SetCurrentShape(NULL); 203 } 204 205 // ShapePathListener 206 void SetShape(Shape* shape) 207 { 208 if (fShape == shape) 209 return; 210 211 if (fShape) 212 fShape->Paths()->RemoveListener(this); 213 214 fShape = shape; 215 216 if (fShape) 217 fShape->Paths()->AddListener(this); 218 } 219 220 Shape* CurrentShape() const 221 { 222 return fShape; 223 } 224 225 private: 226 PathListView* fListView; 227 Shape* fShape; 228 }; 229 230 // #pragma mark - 231 232 enum { 233 MSG_ADD = 'addp', 234 235 MSG_ADD_RECT = 'addr', 236 MSG_ADD_CIRCLE = 'addc', 237 MSG_ADD_ARC = 'adda', 238 239 MSG_DUPLICATE = 'dupp', 240 241 MSG_REVERSE = 'rvrs', 242 MSG_CLEAN_UP = 'clup', 243 MSG_ROTATE_INDICES = 'roti', 244 245 MSG_REMOVE = 'remp', 246 }; 247 248 // constructor 249 PathListView::PathListView(BRect frame, 250 const char* name, 251 BMessage* message, BHandler* target) 252 : SimpleListView(frame, name, 253 NULL, B_SINGLE_SELECTION_LIST), 254 fMessage(message), 255 fMenu(NULL), 256 257 fPathContainer(NULL), 258 fShapeContainer(NULL), 259 fCommandStack(NULL), 260 261 fCurrentShape(NULL), 262 fShapePathListener(new ShapePathListener(this)) 263 { 264 SetTarget(target); 265 } 266 267 // destructor 268 PathListView::~PathListView() 269 { 270 _MakeEmpty(); 271 delete fMessage; 272 273 if (fPathContainer) 274 fPathContainer->RemoveListener(this); 275 276 if (fShapeContainer) 277 fShapeContainer->RemoveListener(fShapePathListener); 278 279 delete fShapePathListener; 280 } 281 282 // SelectionChanged 283 void 284 PathListView::SelectionChanged() 285 { 286 SimpleListView::SelectionChanged(); 287 288 if (!fSyncingToSelection) { 289 // NOTE: single selection list 290 PathListItem* item 291 = dynamic_cast<PathListItem*>(ItemAt(CurrentSelection(0))); 292 if (fMessage) { 293 BMessage message(*fMessage); 294 message.AddPointer("path", item ? (void*)item->path : NULL); 295 Invoke(&message); 296 } 297 } 298 299 _UpdateMenu(); 300 } 301 302 // MouseDown 303 void 304 PathListView::MouseDown(BPoint where) 305 { 306 if (!fCurrentShape) { 307 SimpleListView::MouseDown(where); 308 return; 309 } 310 311 bool handled = false; 312 int32 index = IndexOf(where); 313 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index)); 314 if (item) { 315 BRect itemFrame(ItemFrame(index)); 316 itemFrame.right = itemFrame.left 317 + kBorderOffset + kMarkWidth 318 + kTextOffset / 2.0; 319 VectorPath* path = item->path; 320 if (itemFrame.Contains(where) && fCommandStack) { 321 // add or remove the path to the shape 322 ::Command* command; 323 if (fCurrentShape->Paths()->HasPath(path)) { 324 command = new UnassignPathCommand( 325 fCurrentShape, path); 326 } else { 327 VectorPath* paths[1]; 328 paths[0] = path; 329 command = new AddPathsCommand( 330 fCurrentShape->Paths(), 331 paths, 1, false, 332 fCurrentShape->Paths()->CountPaths()); 333 } 334 fCommandStack->Perform(command); 335 handled = true; 336 } 337 } 338 339 if (!handled) 340 SimpleListView::MouseDown(where); 341 } 342 343 // MessageReceived 344 void 345 PathListView::MessageReceived(BMessage* message) 346 { 347 switch (message->what) { 348 case MSG_ADD: 349 if (fCommandStack) { 350 VectorPath* path; 351 AddPathsCommand* command; 352 new_path(fPathContainer, &path, &command); 353 fCommandStack->Perform(command); 354 } 355 break; 356 357 case MSG_ADD_RECT: 358 if (fCommandStack) { 359 VectorPath* path; 360 AddPathsCommand* command; 361 new_path(fPathContainer, &path, &command); 362 if (path) { 363 path->AddPoint(BPoint(16, 16)); 364 path->AddPoint(BPoint(16, 48)); 365 path->AddPoint(BPoint(48, 48)); 366 path->AddPoint(BPoint(48, 16)); 367 path->SetClosed(true); 368 } 369 fCommandStack->Perform(command); 370 } 371 break; 372 373 case MSG_ADD_CIRCLE: 374 // TODO: ask for number of secions 375 if (fCommandStack) { 376 VectorPath* path; 377 AddPathsCommand* command; 378 new_path(fPathContainer, &path, &command); 379 if (path) { 380 // add four control points defining a circle: 381 // a 382 // b d 383 // c 384 BPoint a(32, 16); 385 BPoint b(16, 32); 386 BPoint c(32, 48); 387 BPoint d(48, 32); 388 389 path->AddPoint(a); 390 path->AddPoint(b); 391 path->AddPoint(c); 392 path->AddPoint(d); 393 394 path->SetClosed(true); 395 396 float controlDist = 0.552284 * 16; 397 path->SetPoint(0, a, a + BPoint(controlDist, 0.0), 398 a + BPoint(-controlDist, 0.0), true); 399 path->SetPoint(1, b, b + BPoint(0.0, -controlDist), 400 b + BPoint(0.0, controlDist), true); 401 path->SetPoint(2, c, c + BPoint(-controlDist, 0.0), 402 c + BPoint(controlDist, 0.0), true); 403 path->SetPoint(3, d, d + BPoint(0.0, controlDist), 404 d + BPoint(0.0, -controlDist), true); 405 } 406 fCommandStack->Perform(command); 407 } 408 break; 409 410 case MSG_DUPLICATE: 411 if (fCommandStack) { 412 PathListItem* item = dynamic_cast<PathListItem*>( 413 ItemAt(CurrentSelection(0))); 414 if (!item) 415 break; 416 417 VectorPath* path; 418 AddPathsCommand* command; 419 new_path(fPathContainer, &path, &command, item->path); 420 fCommandStack->Perform(command); 421 } 422 break; 423 424 case MSG_REVERSE: 425 if (fCommandStack) { 426 PathListItem* item = dynamic_cast<PathListItem*>( 427 ItemAt(CurrentSelection(0))); 428 if (!item) 429 break; 430 431 ReversePathCommand* command 432 = new (nothrow) ReversePathCommand(item->path); 433 fCommandStack->Perform(command); 434 } 435 break; 436 437 case MSG_CLEAN_UP: 438 if (fCommandStack) { 439 PathListItem* item = dynamic_cast<PathListItem*>( 440 ItemAt(CurrentSelection(0))); 441 if (!item) 442 break; 443 444 CleanUpPathCommand* command 445 = new (nothrow) CleanUpPathCommand(item->path); 446 fCommandStack->Perform(command); 447 } 448 break; 449 450 case MSG_REMOVE: 451 RemoveSelected(); 452 break; 453 454 default: 455 SimpleListView::MessageReceived(message); 456 break; 457 } 458 } 459 460 // MakeDragMessage 461 void 462 PathListView::MakeDragMessage(BMessage* message) const 463 { 464 SimpleListView::MakeDragMessage(message); 465 message->AddPointer("container", fPathContainer); 466 int32 count = CountSelectedItems(); 467 for (int32 i = 0; i < count; i++) { 468 PathListItem* item = dynamic_cast<PathListItem*>( 469 ItemAt(CurrentSelection(i))); 470 if (item) 471 message->AddPointer("path", (void*)item->path); 472 else 473 break; 474 } 475 } 476 477 // AcceptDragMessage 478 bool 479 PathListView::AcceptDragMessage(const BMessage* message) const 480 { 481 return SimpleListView::AcceptDragMessage(message); 482 } 483 484 // SetDropTargetRect 485 void 486 PathListView::SetDropTargetRect(const BMessage* message, BPoint where) 487 { 488 SimpleListView::SetDropTargetRect(message, where); 489 } 490 491 // MoveItems 492 void 493 PathListView::MoveItems(BList& items, int32 toIndex) 494 { 495 if (!fCommandStack || !fPathContainer) 496 return; 497 498 int32 count = items.CountItems(); 499 VectorPath** paths = new (nothrow) VectorPath*[count]; 500 if (!paths) 501 return; 502 503 for (int32 i = 0; i < count; i++) { 504 PathListItem* item 505 = dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i)); 506 paths[i] = item ? item->path : NULL; 507 } 508 509 MovePathsCommand* command 510 = new (nothrow) MovePathsCommand(fPathContainer, 511 paths, count, toIndex); 512 if (!command) { 513 delete[] paths; 514 return; 515 } 516 517 fCommandStack->Perform(command); 518 } 519 520 // CopyItems 521 void 522 PathListView::CopyItems(BList& items, int32 toIndex) 523 { 524 if (!fCommandStack || !fPathContainer) 525 return; 526 527 int32 count = items.CountItems(); 528 VectorPath* paths[count]; 529 530 for (int32 i = 0; i < count; i++) { 531 PathListItem* item 532 = dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i)); 533 paths[i] = item ? new (nothrow) VectorPath(*item->path) : NULL; 534 } 535 536 AddPathsCommand* command 537 = new (nothrow) AddPathsCommand(fPathContainer, 538 paths, count, true, toIndex); 539 if (!command) { 540 for (int32 i = 0; i < count; i++) 541 delete paths[i]; 542 return; 543 } 544 545 fCommandStack->Perform(command); 546 } 547 548 // RemoveItemList 549 void 550 PathListView::RemoveItemList(BList& items) 551 { 552 if (!fCommandStack || !fPathContainer) 553 return; 554 555 int32 count = items.CountItems(); 556 VectorPath* paths[count]; 557 for (int32 i = 0; i < count; i++) { 558 PathListItem* item = dynamic_cast<PathListItem*>( 559 (BListItem*)items.ItemAtFast(i)); 560 if (item) 561 paths[i] = item->path; 562 else 563 paths[i] = NULL; 564 } 565 566 RemovePathsCommand* command 567 = new (nothrow) RemovePathsCommand(fPathContainer, 568 paths, count); 569 fCommandStack->Perform(command); 570 } 571 572 // CloneItem 573 BListItem* 574 PathListView::CloneItem(int32 index) const 575 { 576 if (PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index))) { 577 return new PathListItem(item->path, 578 const_cast<PathListView*>(this), 579 fCurrentShape != NULL); 580 } 581 return NULL; 582 } 583 584 // IndexOfSelectable 585 int32 586 PathListView::IndexOfSelectable(Selectable* selectable) const 587 { 588 VectorPath* path = dynamic_cast<VectorPath*>(selectable); 589 if (!path) 590 return -1; 591 592 for (int32 i = 0; 593 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 594 i++) { 595 if (item->path == path) 596 return i; 597 } 598 599 return -1; 600 } 601 602 // SelectableFor 603 Selectable* 604 PathListView::SelectableFor(BListItem* item) const 605 { 606 PathListItem* pathItem = dynamic_cast<PathListItem*>(item); 607 if (pathItem) 608 return pathItem->path; 609 return NULL; 610 } 611 612 // #pragma mark - 613 614 // PathAdded 615 void 616 PathListView::PathAdded(VectorPath* path, int32 index) 617 { 618 // NOTE: we are in the thread that messed with the 619 // ShapeContainer, so no need to lock the 620 // container, when this is changed to asynchronous 621 // notifications, then it would need to be read-locked! 622 if (!LockLooper()) 623 return; 624 625 if (_AddPath(path, index)) 626 Select(CountItems() - 1); 627 628 UnlockLooper(); 629 } 630 631 // PathRemoved 632 void 633 PathListView::PathRemoved(VectorPath* path) 634 { 635 // NOTE: we are in the thread that messed with the 636 // ShapeContainer, so no need to lock the 637 // container, when this is changed to asynchronous 638 // notifications, then it would need to be read-locked! 639 if (!LockLooper()) 640 return; 641 642 // NOTE: we're only interested in VectorPath objects 643 _RemovePath(path); 644 645 UnlockLooper(); 646 } 647 648 // #pragma mark - 649 650 // SetPathContainer 651 void 652 PathListView::SetPathContainer(PathContainer* container) 653 { 654 if (fPathContainer == container) 655 return; 656 657 // detach from old container 658 if (fPathContainer) 659 fPathContainer->RemoveListener(this); 660 661 _MakeEmpty(); 662 663 fPathContainer = container; 664 665 if (!fPathContainer) 666 return; 667 668 fPathContainer->AddListener(this); 669 670 // sync 671 // if (!fPathContainer->ReadLock()) 672 // return; 673 674 int32 count = fPathContainer->CountPaths(); 675 for (int32 i = 0; i < count; i++) 676 _AddPath(fPathContainer->PathAtFast(i), i); 677 678 // fPathContainer->ReadUnlock(); 679 } 680 681 // SetShapeContainer 682 void 683 PathListView::SetShapeContainer(ShapeContainer* container) 684 { 685 if (fShapeContainer == container) 686 return; 687 688 // detach from old container 689 if (fShapeContainer) 690 fShapeContainer->RemoveListener(fShapePathListener); 691 692 fShapeContainer = container; 693 694 if (fShapeContainer) 695 fShapeContainer->AddListener(fShapePathListener); 696 } 697 698 // SetCommandStack 699 void 700 PathListView::SetCommandStack(CommandStack* stack) 701 { 702 fCommandStack = stack; 703 } 704 705 // SetMenu 706 void 707 PathListView::SetMenu(BMenu* menu) 708 { 709 fMenu = menu; 710 if (!fMenu) 711 return; 712 713 fAddMI = new BMenuItem("Add", new BMessage(MSG_ADD)); 714 fAddRectMI = new BMenuItem("Add Rect", new BMessage(MSG_ADD_RECT)); 715 fAddCircleMI = new BMenuItem("Add Circle"B_UTF8_ELLIPSIS, 716 new BMessage(MSG_ADD_CIRCLE)); 717 fAddArcMI = new BMenuItem("Add Arc"B_UTF8_ELLIPSIS, 718 new BMessage(MSG_ADD_ARC)); 719 fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE)); 720 fReverseMI = new BMenuItem("Reverse", new BMessage(MSG_REVERSE)); 721 fCleanUpMI = new BMenuItem("Clean Up", new BMessage(MSG_CLEAN_UP)); 722 fRotateIndicesMI = new BMenuItem("Rotate Indices", 723 new BMessage(MSG_ROTATE_INDICES)); 724 fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE)); 725 726 fMenu->AddItem(fAddMI); 727 fMenu->AddItem(fAddRectMI); 728 fMenu->AddItem(fAddCircleMI); 729 fMenu->AddItem(fAddArcMI); 730 fAddArcMI->SetEnabled(false); 731 732 fMenu->AddSeparatorItem(); 733 734 fMenu->AddItem(fDuplicateMI); 735 fMenu->AddItem(fReverseMI); 736 fMenu->AddItem(fCleanUpMI); 737 fMenu->AddItem(fRotateIndicesMI); 738 739 fMenu->AddSeparatorItem(); 740 741 fMenu->AddItem(fRemoveMI); 742 743 fMenu->SetTargetForItems(this); 744 745 _UpdateMenu(); 746 } 747 748 // SetCurrentShape 749 void 750 PathListView::SetCurrentShape(Shape* shape) 751 { 752 if (fCurrentShape == shape) 753 return; 754 755 fCurrentShape = shape; 756 fShapePathListener->SetShape(shape); 757 758 _UpdateMarks(); 759 } 760 761 // #pragma mark - 762 763 // _AddPath 764 bool 765 PathListView::_AddPath(VectorPath* path, int32 index) 766 { 767 if (path) { 768 return AddItem( 769 new PathListItem(path, this, fCurrentShape != NULL), index); 770 } 771 return false; 772 } 773 774 // _RemovePath 775 bool 776 PathListView::_RemovePath(VectorPath* path) 777 { 778 PathListItem* item = _ItemForPath(path); 779 if (item && RemoveItem(item)) { 780 delete item; 781 return true; 782 } 783 return false; 784 } 785 786 // _ItemForPath 787 PathListItem* 788 PathListView::_ItemForPath(VectorPath* path) const 789 { 790 for (int32 i = 0; 791 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 792 i++) { 793 if (item->path == path) 794 return item; 795 } 796 return NULL; 797 } 798 799 // #pragma mark - 800 801 // _UpdateMarks 802 void 803 PathListView::_UpdateMarks() 804 { 805 int32 count = CountItems(); 806 if (fCurrentShape) { 807 // enable display of marks and mark items whoes 808 // path is contained in fCurrentShape 809 for (int32 i = 0; i < count; i++) { 810 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 811 if (!item) 812 continue; 813 item->SetMarkEnabled(true); 814 item->SetMarked(fCurrentShape->Paths()->HasPath(item->path)); 815 } 816 } else { 817 // disable display of marks 818 for (int32 i = 0; i < count; i++) { 819 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 820 if (!item) 821 continue; 822 item->SetMarkEnabled(false); 823 } 824 } 825 826 Invalidate(); 827 } 828 829 // _SetPathMarked 830 void 831 PathListView::_SetPathMarked(VectorPath* path, bool marked) 832 { 833 if (PathListItem* item = _ItemForPath(path)) { 834 item->SetMarked(marked); 835 } 836 } 837 838 // _UpdateMenu 839 void 840 PathListView::_UpdateMenu() 841 { 842 if (!fMenu) 843 return; 844 845 bool gotSelection = CurrentSelection(0) >= 0; 846 847 fDuplicateMI->SetEnabled(gotSelection); 848 fReverseMI->SetEnabled(gotSelection); 849 fRemoveMI->SetEnabled(gotSelection); 850 } 851 852 853