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