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