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