1 /* 2 * Copyright 2006, 2023, Haiku. 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 "ListViews.h" 11 12 #include <malloc.h> 13 #include <stdio.h> 14 #include <typeinfo> 15 16 #include <Bitmap.h> 17 #include <Clipboard.h> 18 #include <Cursor.h> 19 #include <Entry.h> 20 #include <MessageRunner.h> 21 #include <Messenger.h> 22 #include <ScrollBar.h> 23 #include <ScrollView.h> 24 #include <StackOrHeapArray.h> 25 #include <String.h> 26 #include <Window.h> 27 28 #include "cursors.h" 29 30 #include "Selection.h" 31 32 #define MAX_DRAG_HEIGHT 200.0 33 #define ALPHA 170 34 #define TEXT_OFFSET 5.0 35 36 enum { 37 MSG_TICK = 'tick', 38 }; 39 40 41 // #pragma mark - SimpleItem 42 43 44 SimpleItem::SimpleItem(const char *name) 45 : BStringItem(name) 46 { 47 } 48 49 50 SimpleItem::~SimpleItem() 51 { 52 } 53 54 55 void 56 SimpleItem::Draw(BView *owner, BRect frame, uint32 flags) 57 { 58 DrawBackground(owner, frame, flags); 59 // label 60 if (IsSelected()) 61 owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR)); 62 else 63 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 64 font_height fh; 65 owner->GetFontHeight(&fh); 66 const char* text = Text(); 67 BString truncatedString(text); 68 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 69 frame.Width() - TEXT_OFFSET - 4.0); 70 float height = frame.Height(); 71 float textHeight = fh.ascent + fh.descent; 72 BPoint textPoint; 73 textPoint.x = frame.left + TEXT_OFFSET; 74 textPoint.y = frame.top 75 + ceilf(height / 2.0 - textHeight / 2.0 76 + fh.ascent); 77 owner->DrawString(truncatedString.String(), textPoint); 78 } 79 80 81 void 82 SimpleItem::DrawBackground(BView *owner, BRect frame, uint32 flags) 83 { 84 // stroke a blue frame around the item if it's focused 85 if (flags & FLAGS_FOCUSED) { 86 owner->SetLowColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 87 owner->StrokeRect(frame, B_SOLID_LOW); 88 frame.InsetBy(1.0, 1.0); 89 } 90 // figure out bg-color 91 rgb_color color = ui_color(B_LIST_BACKGROUND_COLOR); 92 93 if (IsSelected()) 94 color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 95 96 if (flags & FLAGS_TINTED_LINE) 97 color = tint_color(color, 1.06); 98 99 owner->SetLowColor(color); 100 owner->FillRect(frame, B_SOLID_LOW); 101 } 102 103 104 // #pragma mark - DragSortableListView 105 106 107 DragSortableListView::DragSortableListView(BRect frame, const char* name, 108 list_view_type type, uint32 resizingMode, 109 uint32 flags) 110 : BListView(frame, name, type, resizingMode, flags), 111 fDropRect(0.0, 0.0, -1.0, -1.0), 112 fMouseWheelFilter(NULL), 113 fScrollPulse(NULL), 114 fDropIndex(-1), 115 fLastClickedItem(NULL), 116 fScrollView(NULL), 117 fDragCommand(B_SIMPLE_DATA), 118 fFocusedIndex(-1), 119 120 fSelection(NULL), 121 fSyncingToSelection(false), 122 fModifyingSelection(false) 123 { 124 SetViewColor(B_TRANSPARENT_32_BIT); 125 } 126 127 128 DragSortableListView::~DragSortableListView() 129 { 130 delete fMouseWheelFilter; 131 delete fScrollPulse; 132 133 SetSelection(NULL); 134 } 135 136 137 void 138 DragSortableListView::AttachedToWindow() 139 { 140 if (!fMouseWheelFilter) 141 fMouseWheelFilter = new MouseWheelFilter(this); 142 Window()->AddCommonFilter(fMouseWheelFilter); 143 144 BListView::AttachedToWindow(); 145 146 // work arround a bug in BListView 147 BRect bounds = Bounds(); 148 BListView::FrameResized(bounds.Width(), bounds.Height()); 149 } 150 151 152 void 153 DragSortableListView::DetachedFromWindow() 154 { 155 Window()->RemoveCommonFilter(fMouseWheelFilter); 156 } 157 158 159 void 160 DragSortableListView::FrameResized(float width, float height) 161 { 162 BListView::FrameResized(width, height); 163 } 164 165 166 /* 167 void 168 DragSortableListView::MakeFocus(bool focused) 169 { 170 if (focused != IsFocus()) { 171 Invalidate(); 172 BListView::MakeFocus(focused); 173 } 174 } 175 */ 176 177 178 void 179 DragSortableListView::Draw(BRect updateRect) 180 { 181 int32 firstIndex = IndexOf(updateRect.LeftTop()); 182 int32 lastIndex = IndexOf(updateRect.RightBottom()); 183 if (firstIndex >= 0) { 184 if (lastIndex < firstIndex) 185 lastIndex = CountItems() - 1; 186 // update rect contains items 187 BRect r = updateRect; 188 for (int32 i = firstIndex; i <= lastIndex; i++) { 189 r = ItemFrame(i); 190 DrawListItem(this, i, r); 191 } 192 updateRect.top = r.bottom + 1.0; 193 if (updateRect.IsValid()) { 194 SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR)); 195 FillRect(updateRect, B_SOLID_LOW); 196 } 197 } else { 198 SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR)); 199 FillRect(updateRect, B_SOLID_LOW); 200 } 201 // drop anticipation indication 202 if (fDropRect.IsValid()) { 203 SetHighColor(255, 0, 0, 255); 204 StrokeRect(fDropRect); 205 } 206 /* // focus indication 207 if (IsFocus()) { 208 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 209 StrokeRect(Bounds()); 210 }*/ 211 } 212 213 214 void 215 DragSortableListView::ScrollTo(BPoint where) 216 { 217 uint32 buttons; 218 BPoint point; 219 GetMouse(&point, &buttons, false); 220 uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; 221 MouseMoved(point, transit, &fDragMessageCopy); 222 BListView::ScrollTo(where); 223 } 224 225 226 void 227 DragSortableListView::TargetedByScrollView(BScrollView* scrollView) 228 { 229 fScrollView = scrollView; 230 BListView::TargetedByScrollView(scrollView); 231 } 232 233 234 bool 235 DragSortableListView::InitiateDrag(BPoint point, int32 index, bool) 236 { 237 // supress drag&drop while an item is focused 238 if (fFocusedIndex >= 0) 239 return false; 240 241 bool success = false; 242 BListItem* item = ItemAt(CurrentSelection(0)); 243 if (!item) { 244 // workarround a timing problem 245 Select(index); 246 item = ItemAt(index); 247 } 248 if (item) { 249 // create drag message 250 BMessage msg(fDragCommand); 251 MakeDragMessage(&msg); 252 // figure out drag rect 253 float width = Bounds().Width(); 254 BRect dragRect(0.0, 0.0, width, -1.0); 255 // figure out, how many items fit into our bitmap 256 int32 numItems; 257 bool fade = false; 258 for (numItems = 0; BListItem* item = ItemAt(CurrentSelection(numItems)); numItems++) { 259 dragRect.bottom += ceilf(item->Height()) + 1.0; 260 if (dragRect.Height() > MAX_DRAG_HEIGHT) { 261 fade = true; 262 dragRect.bottom = MAX_DRAG_HEIGHT; 263 numItems++; 264 break; 265 } 266 } 267 BBitmap* dragBitmap = new BBitmap(dragRect, B_RGB32, true); 268 if (dragBitmap && dragBitmap->IsValid()) { 269 if (BView *v = new BView(dragBitmap->Bounds(), "helper", 270 B_FOLLOW_NONE, B_WILL_DRAW)) { 271 dragBitmap->AddChild(v); 272 dragBitmap->Lock(); 273 BRect itemBounds(dragRect) ; 274 itemBounds.bottom = 0.0; 275 // let all selected items, that fit into our drag_bitmap, draw 276 for (int32 i = 0; i < numItems; i++) { 277 int32 index = CurrentSelection(i); 278 BListItem* item = ItemAt(index); 279 itemBounds.bottom = itemBounds.top + ceilf(item->Height()); 280 if (itemBounds.bottom > dragRect.bottom) 281 itemBounds.bottom = dragRect.bottom; 282 DrawListItem(v, index, itemBounds); 283 itemBounds.top = itemBounds.bottom + 1.0; 284 } 285 // make a black frame arround the edge 286 v->SetHighColor(0, 0, 0, 255); 287 v->StrokeRect(v->Bounds()); 288 v->Sync(); 289 290 uint8 *bits = (uint8 *)dragBitmap->Bits(); 291 int32 height = (int32)dragBitmap->Bounds().Height() + 1; 292 int32 width = (int32)dragBitmap->Bounds().Width() + 1; 293 int32 bpr = dragBitmap->BytesPerRow(); 294 295 if (fade) { 296 for (int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr) { 297 uint8 *line = bits + 3; 298 for (uint8 *end = line + 4 * width; line < end; line += 4) 299 *line = ALPHA; 300 } 301 for (int32 y = height - ALPHA / 2; y < height; y++, bits += bpr) { 302 uint8 *line = bits + 3; 303 for (uint8 *end = line + 4 * width; line < end; line += 4) 304 *line = (height - y) << 1; 305 } 306 } else { 307 for (int32 y = 0; y < height; y++, bits += bpr) { 308 uint8 *line = bits + 3; 309 for (uint8 *end = line + 4 * width; line < end; line += 4) 310 *line = ALPHA; 311 } 312 } 313 dragBitmap->Unlock(); 314 } 315 } else { 316 delete dragBitmap; 317 dragBitmap = NULL; 318 } 319 if (dragBitmap) 320 DragMessage(&msg, dragBitmap, B_OP_ALPHA, BPoint(0.0, 0.0)); 321 else 322 DragMessage(&msg, dragRect.OffsetToCopy(point), this); 323 324 _SetDragMessage(&msg); 325 success = true; 326 } 327 return success; 328 } 329 330 331 void 332 DragSortableListView::WindowActivated(bool active) 333 { 334 // workarround for buggy focus indication of BScrollView 335 if (BView* view = Parent()) 336 view->Invalidate(); 337 } 338 339 340 void 341 DragSortableListView::MessageReceived(BMessage* message) 342 { 343 if (message->what == fDragCommand) { 344 int32 count = CountItems(); 345 if (fDropIndex < 0 || fDropIndex > count) 346 fDropIndex = count; 347 HandleDropMessage(message, fDropIndex); 348 fDropIndex = -1; 349 } else { 350 switch (message->what) { 351 case MSG_TICK: { 352 float scrollV = 0.0; 353 BRect rect(Bounds()); 354 BPoint point; 355 uint32 buttons; 356 GetMouse(&point, &buttons, false); 357 if (rect.Contains(point)) { 358 // calculate the vertical scrolling offset 359 float hotDist = rect.Height() * SCROLL_AREA; 360 if (point.y > rect.bottom - hotDist) 361 scrollV = hotDist - (rect.bottom - point.y); 362 else if (point.y < rect.top + hotDist) 363 scrollV = (point.y - rect.top) - hotDist; 364 } 365 // scroll 366 if (scrollV != 0.0 && fScrollView) { 367 if (BScrollBar* scrollBar = fScrollView->ScrollBar(B_VERTICAL)) { 368 float value = scrollBar->Value(); 369 scrollBar->SetValue(scrollBar->Value() + scrollV); 370 if (scrollBar->Value() != value) { 371 // update mouse position 372 uint32 buttons; 373 BPoint point; 374 GetMouse(&point, &buttons, false); 375 uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; 376 MouseMoved(point, transit, &fDragMessageCopy); 377 } 378 } 379 } 380 break; 381 } 382 // case B_MODIFIERS_CHANGED: 383 // ModifiersChanged(); 384 // break; 385 case B_MOUSE_WHEEL_CHANGED: { 386 BListView::MessageReceived(message); 387 BPoint point; 388 uint32 buttons; 389 GetMouse(&point, &buttons, false); 390 uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; 391 MouseMoved(point, transit, &fDragMessageCopy); 392 break; 393 } 394 default: 395 BListView::MessageReceived(message); 396 break; 397 } 398 } 399 } 400 401 402 void 403 DragSortableListView::KeyDown(const char* bytes, int32 numBytes) 404 { 405 if (numBytes < 1) 406 return; 407 408 if ((bytes[0] == B_BACKSPACE) || (bytes[0] == B_DELETE)) 409 RemoveSelected(); 410 411 BListView::KeyDown(bytes, numBytes); 412 } 413 414 415 void 416 DragSortableListView::MouseDown(BPoint where) 417 { 418 int32 clicks = 1; 419 uint32 buttons = 0; 420 Window()->CurrentMessage()->FindInt32("clicks", &clicks); 421 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 422 int32 clickedIndex = -1; 423 for (int32 i = 0; BListItem* item = ItemAt(i); i++) { 424 if (ItemFrame(i).Contains(where)) { 425 if (clicks == 2) { 426 // only do something if user clicked the same item twice 427 if (fLastClickedItem == item) 428 DoubleClicked(i); 429 } else { 430 // remember last clicked item 431 fLastClickedItem = item; 432 } 433 clickedIndex = i; 434 break; 435 } 436 } 437 if (clickedIndex == -1) 438 fLastClickedItem = NULL; 439 440 BListItem* item = ItemAt(clickedIndex); 441 if (ListType() == B_MULTIPLE_SELECTION_LIST 442 && item && (buttons & B_SECONDARY_MOUSE_BUTTON)) { 443 if (item->IsSelected()) 444 Deselect(clickedIndex); 445 else 446 Select(clickedIndex, true); 447 } else { 448 BListView::MouseDown(where); 449 } 450 } 451 452 453 void 454 DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg) 455 { 456 if (msg && AcceptDragMessage(msg)) { 457 switch (transit) { 458 case B_ENTERED_VIEW: 459 case B_INSIDE_VIEW: { 460 // remember drag message 461 // this is needed to react on modifier changes 462 _SetDragMessage(msg); 463 // set drop target through virtual function 464 SetDropTargetRect(msg, where); 465 // go into autoscrolling mode 466 BRect r = Bounds(); 467 r.InsetBy(0.0, r.Height() * SCROLL_AREA); 468 SetAutoScrolling(!r.Contains(where)); 469 break; 470 } 471 case B_EXITED_VIEW: 472 // forget drag message 473 _SetDragMessage(NULL); 474 SetAutoScrolling(false); 475 // fall through 476 case B_OUTSIDE_VIEW: 477 _RemoveDropAnticipationRect(); 478 break; 479 } 480 } else { 481 _RemoveDropAnticipationRect(); 482 BListView::MouseMoved(where, transit, msg); 483 _SetDragMessage(NULL); 484 SetAutoScrolling(false); 485 486 BCursor cursor(B_HAND_CURSOR); 487 SetViewCursor(&cursor, true); 488 } 489 fLastMousePos = where; 490 } 491 492 493 void 494 DragSortableListView::MouseUp(BPoint where) 495 { 496 // remove drop mark 497 _SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0)); 498 SetAutoScrolling(false); 499 // be sure to forget drag message 500 _SetDragMessage(NULL); 501 BListView::MouseUp(where); 502 503 BCursor cursor(B_HAND_CURSOR); 504 SetViewCursor(&cursor, true); 505 } 506 507 508 void 509 DragSortableListView::DrawItem(BListItem *item, BRect itemFrame, bool complete) 510 { 511 DrawListItem(this, IndexOf(item), itemFrame); 512 /* if (IsFocus()) { 513 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 514 StrokeRect(Bounds()); 515 }*/ 516 } 517 518 519 bool 520 DragSortableListView::MouseWheelChanged(float x, float y) 521 { 522 BPoint where; 523 uint32 buttons; 524 GetMouse(&where, &buttons, false); 525 if (Bounds().Contains(where)) 526 return true; 527 else 528 return false; 529 } 530 531 532 // #pragma mark - 533 534 535 void 536 DragSortableListView::ObjectChanged(const Observable* object) 537 { 538 if (object != fSelection || fModifyingSelection || fSyncingToSelection) 539 return; 540 541 //printf("%s - syncing start\n", Name()); 542 fSyncingToSelection = true; 543 544 // try to sync to Selection 545 BList selectedItems; 546 547 int32 count = fSelection->CountSelected(); 548 for (int32 i = 0; i < count; i++) { 549 int32 index = IndexOfSelectable(fSelection->SelectableAtFast(i)); 550 if (index >= 0) { 551 BListItem* item = ItemAt(index); 552 if (item && !selectedItems.HasItem((void*)item)) 553 selectedItems.AddItem((void*)item); 554 } 555 } 556 557 count = selectedItems.CountItems(); 558 if (count == 0) { 559 if (CurrentSelection(0) >= 0) 560 DeselectAll(); 561 } else { 562 count = CountItems(); 563 for (int32 i = 0; i < count; i++) { 564 BListItem* item = ItemAt(i); 565 bool selected = selectedItems.RemoveItem((void*)item); 566 if (item->IsSelected() != selected) { 567 Select(i, true); 568 } 569 } 570 } 571 572 fSyncingToSelection = false; 573 //printf("%s - done\n", Name()); 574 } 575 576 577 // #pragma mark - 578 579 580 void 581 DragSortableListView::SetDragCommand(uint32 command) 582 { 583 fDragCommand = command; 584 } 585 586 587 void 588 DragSortableListView::ModifiersChanged() 589 { 590 SetDropTargetRect(&fDragMessageCopy, fLastMousePos); 591 } 592 593 594 void 595 DragSortableListView::SetItemFocused(int32 index) 596 { 597 InvalidateItem(fFocusedIndex); 598 InvalidateItem(index); 599 fFocusedIndex = index; 600 } 601 602 603 bool 604 DragSortableListView::AcceptDragMessage(const BMessage* message) const 605 { 606 return message->what == fDragCommand; 607 } 608 609 610 void 611 DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where) 612 613 { 614 if (AcceptDragMessage(message)) { 615 bool copy = modifiers() & B_SHIFT_KEY; 616 bool replaceAll = !message->HasPointer("list") && !copy; 617 BRect r = Bounds(); 618 if (replaceAll) { 619 r.bottom--; // compensate for scrollbar offset 620 _SetDropAnticipationRect(r); 621 fDropIndex = -1; 622 } else { 623 // offset where by half of item height 624 r = ItemFrame(0); 625 where.y += r.Height() / 2.0; 626 627 int32 index = IndexOf(where); 628 if (index < 0) 629 index = CountItems(); 630 _SetDropIndex(index); 631 632 const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR; 633 BCursor cursor(cursorData); 634 SetViewCursor(&cursor, true); 635 } 636 } 637 } 638 639 640 bool 641 DragSortableListView::HandleDropMessage(const BMessage* message, 642 int32 dropIndex) 643 { 644 DragSortableListView *list = NULL; 645 if (message->FindPointer("list", (void **)&list) != B_OK || list != this) 646 return false; 647 648 BList items; 649 int32 index; 650 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; i++) { 651 BListItem* item = ItemAt(index); 652 if (item != NULL) 653 items.AddItem((void*)item); 654 } 655 656 if (items.CountItems() == 0) 657 return false; 658 659 if ((modifiers() & B_SHIFT_KEY) != 0) 660 CopyItems(items, dropIndex); 661 else 662 MoveItems(items, dropIndex); 663 664 return true; 665 } 666 667 668 void 669 DragSortableListView::SetAutoScrolling(bool enable) 670 { 671 if (fScrollPulse && enable) 672 return; 673 if (enable) { 674 BMessenger messenger(this, Window()); 675 BMessage message(MSG_TICK); 676 fScrollPulse = new BMessageRunner(messenger, &message, 40000LL); 677 } else { 678 delete fScrollPulse; 679 fScrollPulse = NULL; 680 } 681 } 682 683 684 bool 685 DragSortableListView::DoesAutoScrolling() const 686 { 687 return fScrollPulse; 688 } 689 690 691 void 692 DragSortableListView::ScrollTo(int32 index) 693 { 694 if (index < 0) 695 index = 0; 696 if (index >= CountItems()) 697 index = CountItems() - 1; 698 699 if (ItemAt(index)) { 700 BRect itemFrame = ItemFrame(index); 701 BRect bounds = Bounds(); 702 if (itemFrame.top < bounds.top) { 703 ScrollTo(itemFrame.LeftTop()); 704 } else if (itemFrame.bottom > bounds.bottom) { 705 ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height())); 706 } 707 } 708 } 709 710 711 void 712 DragSortableListView::MoveItems(BList& items, int32 index) 713 { 714 DeselectAll(); 715 // we remove the items while we look at them, the insertion index is decreased 716 // when the items index is lower, so that we insert at the right spot after 717 // removal 718 BList removedItems; 719 int32 count = items.CountItems(); 720 for (int32 i = 0; i < count; i++) { 721 BListItem* item = (BListItem*)items.ItemAt(i); 722 int32 removeIndex = IndexOf(item); 723 if (RemoveItem(item) && removedItems.AddItem((void*)item)) { 724 if (removeIndex < index) 725 index--; 726 } 727 // else ??? -> blow up 728 } 729 for (int32 i = 0; 730 BListItem* item = (BListItem*)removedItems.ItemAt(i); i++) { 731 if (AddItem(item, index)) { 732 // after we're done, the newly inserted items will be selected 733 Select(index, true); 734 // next items will be inserted after this one 735 index++; 736 } else 737 delete item; 738 } 739 } 740 741 742 void 743 DragSortableListView::CopyItems(BList& items, int32 index) 744 { 745 DeselectAll(); 746 // by inserting the items after we copied all items first, we avoid 747 // cloning an item we already inserted and messing everything up 748 // in other words, don't touch the list before we know which items 749 // need to be cloned 750 BList clonedItems; 751 int32 count = items.CountItems(); 752 for (int32 i = 0; i < count; i++) { 753 BListItem* item = CloneItem(IndexOf((BListItem*)items.ItemAt(i))); 754 if (item && !clonedItems.AddItem((void*)item)) 755 delete item; 756 } 757 for (int32 i = 0; 758 BListItem* item = (BListItem*)clonedItems.ItemAt(i); i++) { 759 if (AddItem(item, index)) { 760 // after we're done, the newly inserted items will be selected 761 Select(index, true); 762 // next items will be inserted after this one 763 index++; 764 } else 765 delete item; 766 } 767 } 768 769 770 void 771 DragSortableListView::RemoveItemList(BList& items) 772 { 773 int32 count = items.CountItems(); 774 for (int32 i = 0; i < count; i++) { 775 BListItem* item = (BListItem*)items.ItemAt(i); 776 if (RemoveItem(item)) 777 delete item; 778 } 779 } 780 781 782 void 783 DragSortableListView::RemoveSelected() 784 { 785 // if (fFocusedIndex >= 0) 786 // return; 787 788 BList items; 789 for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++) 790 items.AddItem((void*)item); 791 RemoveItemList(items); 792 } 793 794 795 // #pragma mark - 796 797 798 void 799 DragSortableListView::SetSelection(Selection* selection) 800 { 801 if (fSelection == selection) 802 return; 803 804 if (fSelection) 805 fSelection->RemoveObserver(this); 806 807 fSelection = selection; 808 809 if (fSelection) 810 fSelection->AddObserver(this); 811 } 812 813 814 int32 815 DragSortableListView::IndexOfSelectable(Selectable* selectable) const 816 { 817 return -1; 818 } 819 820 821 Selectable* 822 DragSortableListView::SelectableFor(BListItem* item) const 823 { 824 return NULL; 825 } 826 827 828 void 829 DragSortableListView::SelectAll() 830 { 831 Select(0, CountItems() - 1); 832 } 833 834 835 int32 836 DragSortableListView::CountSelectedItems() const 837 { 838 int32 count = 0; 839 while (CurrentSelection(count) >= 0) 840 count++; 841 return count; 842 } 843 844 845 void 846 DragSortableListView::SelectionChanged() 847 { 848 //printf("%s::SelectionChanged()", typeid(*this).name()); 849 // modify global Selection 850 if (!fSelection || fSyncingToSelection) 851 return; 852 853 fModifyingSelection = true; 854 855 BList selectables; 856 for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++) { 857 Selectable* selectable = SelectableFor(item); 858 if (selectable) 859 selectables.AddItem((void*)selectable); 860 } 861 862 AutoNotificationSuspender _(fSelection); 863 864 int32 count = selectables.CountItems(); 865 if (count == 0) { 866 //printf(" deselecting all\n"); 867 if (!fSyncingToSelection) 868 fSelection->DeselectAll(); 869 } else { 870 //printf(" selecting %ld items\n", count); 871 for (int32 i = 0; i < count; i++) { 872 Selectable* selectable = (Selectable*)selectables.ItemAtFast(i); 873 fSelection->Select(selectable, i > 0); 874 } 875 } 876 877 fModifyingSelection = false; 878 } 879 880 881 // #pragma mark - 882 883 884 bool 885 DragSortableListView::DeleteItem(int32 index) 886 { 887 BListItem* item = ItemAt(index); 888 if (item && RemoveItem(item)) { 889 delete item; 890 return true; 891 } 892 return false; 893 } 894 895 896 void 897 DragSortableListView::_SetDropAnticipationRect(BRect r) 898 { 899 if (fDropRect != r) { 900 if (fDropRect.IsValid()) 901 Invalidate(fDropRect); 902 fDropRect = r; 903 if (fDropRect.IsValid()) 904 Invalidate(fDropRect); 905 } 906 } 907 908 909 void 910 DragSortableListView::_SetDropIndex(int32 index) 911 { 912 if (fDropIndex != index) { 913 fDropIndex = index; 914 if (fDropIndex >= 0) { 915 int32 count = CountItems(); 916 if (fDropIndex == count) { 917 BRect r; 918 if (ItemAt(count - 1)) { 919 r = ItemFrame(count - 1); 920 r.top = r.bottom; 921 r.bottom = r.top + 1.0; 922 } else { 923 r = Bounds(); 924 r.bottom--; // compensate for scrollbars moved slightly out of window 925 } 926 _SetDropAnticipationRect(r); 927 } else { 928 BRect r = ItemFrame(fDropIndex); 929 r.top--; 930 r.bottom = r.top + 1.0; 931 _SetDropAnticipationRect(r); 932 } 933 } 934 } 935 } 936 937 938 void 939 DragSortableListView::_RemoveDropAnticipationRect() 940 { 941 _SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0)); 942 // _SetDropIndex(-1); 943 } 944 945 946 void 947 DragSortableListView::_SetDragMessage(const BMessage* message) 948 { 949 if (message) 950 fDragMessageCopy = *message; 951 else 952 fDragMessageCopy.what = 0; 953 } 954 955 956 // #pragma mark - SimpleListView 957 958 959 SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage) 960 : DragSortableListView(frame, "playlist listview", 961 B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL, 962 B_WILL_DRAW | B_NAVIGABLE 963 | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), 964 fSelectionChangeMessage(selectionChangeMessage) 965 { 966 } 967 968 969 SimpleListView::SimpleListView(BRect frame, const char* name, 970 BMessage* selectionChangeMessage, 971 list_view_type type, 972 uint32 resizingMode, uint32 flags) 973 : DragSortableListView(frame, name, type, resizingMode, flags), 974 fSelectionChangeMessage(selectionChangeMessage) 975 { 976 } 977 978 979 SimpleListView::~SimpleListView() 980 { 981 delete fSelectionChangeMessage; 982 } 983 984 985 #ifdef LIB_LAYOUT 986 987 minimax 988 SimpleListView::layoutprefs() 989 { 990 mpm.mini.x = 30.0; 991 mpm.maxi.x = 10000.0; 992 mpm.mini.y = 50.0; 993 mpm.maxi.y = 10000.0; 994 mpm.weight = 1.0; 995 return mpm; 996 } 997 998 999 BRect 1000 SimpleListView::layout(BRect frame) 1001 { 1002 MoveTo(frame.LeftTop()); 1003 ResizeTo(frame.Width(), frame.Height()); 1004 return Frame(); 1005 } 1006 1007 #endif // LIB_LAYOUT 1008 1009 1010 void 1011 SimpleListView::DetachedFromWindow() 1012 { 1013 DragSortableListView::DetachedFromWindow(); 1014 _MakeEmpty(); 1015 } 1016 1017 1018 void 1019 SimpleListView::MessageReceived(BMessage* message) 1020 { 1021 switch (message->what) { 1022 // NOTE: pasting is handled in MainWindow::MessageReceived 1023 case B_COPY: 1024 { 1025 int count = CountSelectedItems(); 1026 if (count == 0) 1027 return; 1028 1029 if (!be_clipboard->Lock()) 1030 break; 1031 be_clipboard->Clear(); 1032 1033 BMessage data; 1034 ArchiveSelection(&data); 1035 1036 ssize_t size = data.FlattenedSize(); 1037 BStackOrHeapArray<char, 1024> archive(size); 1038 if (!archive) { 1039 be_clipboard->Unlock(); 1040 break; 1041 } 1042 data.Flatten(archive, size); 1043 1044 be_clipboard->Data()->AddData( 1045 "application/x-vnd.icon_o_matic-listview-message", B_MIME_TYPE, archive, size); 1046 1047 be_clipboard->Commit(); 1048 be_clipboard->Unlock(); 1049 1050 break; 1051 } 1052 1053 default: 1054 DragSortableListView::MessageReceived(message); 1055 break; 1056 } 1057 } 1058 1059 1060 BListItem* 1061 SimpleListView::CloneItem(int32 atIndex) const 1062 { 1063 BListItem* clone = NULL; 1064 if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(atIndex))) 1065 clone = new SimpleItem(item->Text()); 1066 return clone; 1067 } 1068 1069 1070 void 1071 SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const 1072 { 1073 if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(index))) { 1074 uint32 flags = FLAGS_NONE; 1075 if (index == fFocusedIndex) 1076 flags |= FLAGS_FOCUSED; 1077 if (index % 2) 1078 flags |= FLAGS_TINTED_LINE; 1079 item->Draw(owner, frame, flags); 1080 } 1081 } 1082 1083 1084 void 1085 SimpleListView::MakeDragMessage(BMessage* message) const 1086 { 1087 if (message) { 1088 message->AddPointer("list", 1089 (void*)dynamic_cast<const DragSortableListView*>(this)); 1090 int32 index; 1091 for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++) 1092 message->AddInt32("index", index); 1093 } 1094 1095 BMessage items; 1096 ArchiveSelection(&items); 1097 message->AddMessage("items", &items); 1098 } 1099 1100 1101 bool 1102 SimpleListView::HandleDropMessage(const BMessage* message, int32 dropIndex) 1103 { 1104 // Let DragSortableListView handle drag-sorting (when drag came from ourself) 1105 if (DragSortableListView::HandleDropMessage(message, dropIndex)) 1106 return true; 1107 1108 BMessage items; 1109 if (message->FindMessage("items", &items) != B_OK) 1110 return false; 1111 1112 return InstantiateSelection(&items, dropIndex); 1113 } 1114 1115 1116 bool 1117 SimpleListView::HandlePaste(const BMessage* archive) 1118 { 1119 return InstantiateSelection(archive, CountItems()); 1120 } 1121 1122 1123 void 1124 SimpleListView::_MakeEmpty() 1125 { 1126 // NOTE: BListView::MakeEmpty() uses ScrollTo() 1127 // for which the object needs to be attached to 1128 // a BWindow.... :-( 1129 int32 count = CountItems(); 1130 for (int32 i = count - 1; i >= 0; i--) 1131 delete RemoveItem(i); 1132 } 1133 1134