xref: /haiku/src/apps/icon-o-matic/generic/gui/ListViews.cpp (revision da4dbfa47a47beb355289f3dd685797cee69ab77)
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