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
37static const rgb_color kDropIndicatorColor = make_color(255, 65, 54, 255);
38static const rgb_color kDragFrameColor = make_color(17, 17, 17, 255);
39
40
41// #pragma mark - SimpleItem
42
43
44SimpleItem::SimpleItem(const char *name)
45	:
46	BStringItem(name)
47{
48}
49
50
51SimpleItem::~SimpleItem()
52{
53}
54
55
56void
57SimpleItem::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
86void
87SimpleItem::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
114DragSortableListView::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
134DragSortableListView::~DragSortableListView()
135{
136	delete fMouseWheelFilter;
137
138	SetSelection(NULL);
139}
140
141
142void
143DragSortableListView::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
157void
158DragSortableListView::DetachedFromWindow()
159{
160	Window()->RemoveCommonFilter(fMouseWheelFilter);
161}
162
163
164void
165DragSortableListView::FrameResized(float width, float height)
166{
167	BListView::FrameResized(width, height);
168}
169
170
171void
172DragSortableListView::TargetedByScrollView(BScrollView* scrollView)
173{
174	fScrollView = scrollView;
175	BListView::TargetedByScrollView(scrollView);
176}
177
178
179void
180DragSortableListView::WindowActivated(bool active)
181{
182	// work-around for buggy focus indicator on BScrollView
183	if (BView* view = Parent())
184		view->Invalidate();
185}
186
187
188void
189DragSortableListView::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
218void
219DragSortableListView::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
231void
232DragSortableListView::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
267void
268DragSortableListView::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
309void
310DragSortableListView::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
324bool
325DragSortableListView::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
340void
341DragSortableListView::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
385void
386DragSortableListView::SetDragCommand(uint32 command)
387{
388	fDragCommand = command;
389}
390
391
392void
393DragSortableListView::ModifiersChanged()
394{
395	SetDropTargetRect(&fDragMessageCopy, fLastMousePos);
396}
397
398
399void
400DragSortableListView::SetItemFocused(int32 index)
401{
402	InvalidateItem(fFocusedIndex);
403	InvalidateItem(index);
404	fFocusedIndex = index;
405}
406
407
408bool
409DragSortableListView::AcceptDragMessage(const BMessage* message) const
410{
411	return message->what == fDragCommand;
412}
413
414
415void
416DragSortableListView::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
445bool
446DragSortableListView::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
473bool
474DragSortableListView::DoesAutoScrolling() const
475{
476	return true;
477}
478
479
480void
481DragSortableListView::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
511void
512DragSortableListView::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
539void
540DragSortableListView::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
551void
552DragSortableListView::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
567void
568DragSortableListView::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
583int32
584DragSortableListView::IndexOfSelectable(Selectable* selectable) const
585{
586	return -1;
587}
588
589
590Selectable*
591DragSortableListView::SelectableFor(BListItem* item) const
592{
593	return NULL;
594}
595
596
597void
598DragSortableListView::SelectAll()
599{
600	Select(0, CountItems() - 1);
601}
602
603
604int32
605DragSortableListView::CountSelectedItems() const
606{
607	int32 count = 0;
608	while (CurrentSelection(count) >= 0)
609		count++;
610	return count;
611}
612
613
614void
615DragSortableListView::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
653bool
654DragSortableListView::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
665void
666DragSortableListView::SetDropRect(BRect rect)
667{
668	fDropRect = rect;
669}
670
671
672void
673DragSortableListView::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
702void
703DragSortableListView::InvalidateDropRect()
704{
705	fDropRect = BRect(0, 0, -1, -1);
706//	SetDropIndex(-1);
707}
708
709
710void
711DragSortableListView::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
723SimpleListView::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
733SimpleListView::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
743SimpleListView::~SimpleListView()
744{
745	delete fSelectionChangeMessage;
746}
747
748
749void
750SimpleListView::DetachedFromWindow()
751{
752	DragSortableListView::DetachedFromWindow();
753	_MakeEmpty();
754}
755
756
757void
758SimpleListView::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
801bool
802SimpleListView::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
903void
904SimpleListView::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
945BListItem*
946SimpleListView::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
955void
956SimpleListView::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
972bool
973SimpleListView::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
987bool
988SimpleListView::HandlePaste(const BMessage* archive)
989{
990	return InstantiateSelection(archive, CountItems());
991}
992
993
994void
995SimpleListView::_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