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