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