xref: /haiku/src/apps/icon-o-matic/generic/gui/ListViews.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
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 || fSyncingToSelection)
537 		return;
538 
539 //printf("%s - syncing start\n", Name());
540 	fSyncingToSelection = true;
541 
542 	// try to sync to Selection
543 	BList selectedItems;
544 
545 	int32 count = fSelection->CountSelected();
546 	for (int32 i = 0; i < count; i++) {
547 		int32 index = IndexOfSelectable(fSelection->SelectableAtFast(i));
548 		if (index >= 0) {
549 			BListItem* item = ItemAt(index);
550 			if (item && !selectedItems.HasItem((void*)item))
551 				selectedItems.AddItem((void*)item);
552 		}
553 	}
554 
555 	count = selectedItems.CountItems();
556 	if (count == 0) {
557 		if (CurrentSelection(0) >= 0)
558 			DeselectAll();
559 	} else {
560 		count = CountItems();
561 		for (int32 i = 0; i < count; i++) {
562 			BListItem* item = ItemAt(i);
563 			bool selected = selectedItems.RemoveItem((void*)item);
564 			if (item->IsSelected() != selected) {
565 				Select(i, true);
566 			}
567 		}
568 	}
569 
570 	fSyncingToSelection = false;
571 //printf("%s - done\n", Name());
572 }
573 
574 // #pragma mark -
575 
576 // SetDragCommand
577 void
578 DragSortableListView::SetDragCommand(uint32 command)
579 {
580 	fDragCommand = command;
581 }
582 
583 // ModifiersChaned
584 void
585 DragSortableListView::ModifiersChanged()
586 {
587 	SetDropTargetRect(&fDragMessageCopy, fLastMousePos);
588 }
589 
590 // SetItemFocused
591 void
592 DragSortableListView::SetItemFocused(int32 index)
593 {
594 	InvalidateItem(fFocusedIndex);
595 	InvalidateItem(index);
596 	fFocusedIndex = index;
597 }
598 
599 // AcceptDragMessage
600 bool
601 DragSortableListView::AcceptDragMessage(const BMessage* message) const
602 {
603 	return message->what == fDragCommand;
604 }
605 
606 // SetDropTargetRect
607 void
608 DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where)
609 
610 {
611 	if (AcceptDragMessage(message)) {
612 		bool copy = modifiers() & B_SHIFT_KEY;
613 		bool replaceAll = !message->HasPointer("list") && !copy;
614 		BRect r = Bounds();
615 		if (replaceAll) {
616 			r.bottom--;	// compensate for scrollbar offset
617 			_SetDropAnticipationRect(r);
618 			fDropIndex = -1;
619 		} else {
620 			// offset where by half of item height
621 			r = ItemFrame(0);
622 			where.y += r.Height() / 2.0;
623 
624 			int32 index = IndexOf(where);
625 			if (index < 0)
626 				index = CountItems();
627 			_SetDropIndex(index);
628 
629 			const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR;
630 			BCursor cursor(cursorData);
631 			SetViewCursor(&cursor, true);
632 		}
633 	}
634 }
635 
636 // SetAutoScrolling
637 void
638 DragSortableListView::SetAutoScrolling(bool enable)
639 {
640 	if (fScrollPulse && enable)
641 		return;
642 	if (enable) {
643 		BMessenger messenger(this, Window());
644 		BMessage message(MSG_TICK);
645 		fScrollPulse = new BMessageRunner(messenger, &message, 40000LL);
646 	} else {
647 		delete fScrollPulse;
648 		fScrollPulse = NULL;
649 	}
650 }
651 
652 // DoesAutoScrolling
653 bool
654 DragSortableListView::DoesAutoScrolling() const
655 {
656 	return fScrollPulse;
657 }
658 
659 // ScrollTo
660 void
661 DragSortableListView::ScrollTo(int32 index)
662 {
663 	if (index < 0)
664 		index = 0;
665 	if (index >= CountItems())
666 		index = CountItems() - 1;
667 
668 	if (ItemAt(index)) {
669 		BRect itemFrame = ItemFrame(index);
670 		BRect bounds = Bounds();
671 		if (itemFrame.top < bounds.top) {
672 			ScrollTo(itemFrame.LeftTop());
673 		} else if (itemFrame.bottom > bounds.bottom) {
674 			ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height()));
675 		}
676 	}
677 }
678 
679 // MoveItems
680 void
681 DragSortableListView::MoveItems(BList& items, int32 index)
682 {
683 	DeselectAll();
684 	// we remove the items while we look at them, the insertion index is decreased
685 	// when the items index is lower, so that we insert at the right spot after
686 	// removal
687 	BList removedItems;
688 	int32 count = items.CountItems();
689 	for (int32 i = 0; i < count; i++) {
690 		BListItem* item = (BListItem*)items.ItemAt(i);
691 		int32 removeIndex = IndexOf(item);
692 		if (RemoveItem(item) && removedItems.AddItem((void*)item)) {
693 			if (removeIndex < index)
694 				index--;
695 		}
696 		// else ??? -> blow up
697 	}
698 	for (int32 i = 0;
699 		 BListItem* item = (BListItem*)removedItems.ItemAt(i); i++) {
700 		if (AddItem(item, index)) {
701 			// after we're done, the newly inserted items will be selected
702 			Select(index, true);
703 			// next items will be inserted after this one
704 			index++;
705 		} else
706 			delete item;
707 	}
708 }
709 
710 // CopyItems
711 void
712 DragSortableListView::CopyItems(BList& items, int32 index)
713 {
714 	DeselectAll();
715 	// by inserting the items after we copied all items first, we avoid
716 	// cloning an item we already inserted and messing everything up
717 	// in other words, don't touch the list before we know which items
718 	// need to be cloned
719 	BList clonedItems;
720 	int32 count = items.CountItems();
721 	for (int32 i = 0; i < count; i++) {
722 		BListItem* item = CloneItem(IndexOf((BListItem*)items.ItemAt(i)));
723 		if (item && !clonedItems.AddItem((void*)item))
724 			delete item;
725 	}
726 	for (int32 i = 0;
727 		 BListItem* item = (BListItem*)clonedItems.ItemAt(i); i++) {
728 		if (AddItem(item, index)) {
729 			// after we're done, the newly inserted items will be selected
730 			Select(index, true);
731 			// next items will be inserted after this one
732 			index++;
733 		} else
734 			delete item;
735 	}
736 }
737 
738 // RemoveItemList
739 void
740 DragSortableListView::RemoveItemList(BList& items)
741 {
742 	int32 count = items.CountItems();
743 	for (int32 i = 0; i < count; i++) {
744 		BListItem* item = (BListItem*)items.ItemAt(i);
745 		if (RemoveItem(item))
746 			delete item;
747 	}
748 }
749 
750 // RemoveSelected
751 void
752 DragSortableListView::RemoveSelected()
753 {
754 //	if (fFocusedIndex >= 0)
755 //		return;
756 
757 	BList items;
758 	for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++)
759 		items.AddItem((void*)item);
760 	RemoveItemList(items);
761 }
762 
763 // #pragma mark -
764 
765 // SetSelection
766 void
767 DragSortableListView::SetSelection(Selection* selection)
768 {
769 	if (fSelection == selection)
770 		return;
771 
772 	if (fSelection)
773 		fSelection->RemoveObserver(this);
774 
775 	fSelection = selection;
776 
777 	if (fSelection)
778 		fSelection->AddObserver(this);
779 }
780 
781 // IndexOfSelectable
782 int32
783 DragSortableListView::IndexOfSelectable(Selectable* selectable) const
784 {
785 	return -1;
786 }
787 
788 // SelectableFor
789 Selectable*
790 DragSortableListView::SelectableFor(BListItem* item) const
791 {
792 	return NULL;
793 }
794 
795 // SelectAll
796 void
797 DragSortableListView::SelectAll()
798 {
799 	Select(0, CountItems() - 1);
800 }
801 
802 // CountSelectedItems
803 int32
804 DragSortableListView::CountSelectedItems() const
805 {
806 	int32 count = 0;
807 	while (CurrentSelection(count) >= 0)
808 		count++;
809 	return count;
810 }
811 
812 // SelectionChanged
813 void
814 DragSortableListView::SelectionChanged()
815 {
816 //printf("%s::SelectionChanged()", typeid(*this).name());
817 	// modify global Selection
818 	if (!fSelection || fSyncingToSelection)
819 		return;
820 
821 	fModifyingSelection = true;
822 
823 	BList selectables;
824 	for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++) {
825 		Selectable* selectable = SelectableFor(item);
826 		if (selectable)
827 			selectables.AddItem((void*)selectable);
828 	}
829 
830 	AutoNotificationSuspender _(fSelection);
831 
832 	int32 count = selectables.CountItems();
833 	if (count == 0) {
834 //printf("  deselecting all\n");
835 		if (!fSyncingToSelection)
836 			fSelection->DeselectAll();
837 	} else {
838 //printf("  selecting %ld items\n", count);
839 		for (int32 i = 0; i < count; i++) {
840 			Selectable* selectable = (Selectable*)selectables.ItemAtFast(i);
841 			fSelection->Select(selectable, i > 0);
842 		}
843 	}
844 
845 	fModifyingSelection = false;
846 }
847 
848 // #pragma mark -
849 
850 // DeleteItem
851 bool
852 DragSortableListView::DeleteItem(int32 index)
853 {
854 	BListItem* item = ItemAt(index);
855 	if (item && RemoveItem(item)) {
856 		delete item;
857 		return true;
858 	}
859 	return false;
860 }
861 
862 // _SetDropAnticipationRect
863 void
864 DragSortableListView::_SetDropAnticipationRect(BRect r)
865 {
866 	if (fDropRect != r) {
867 		if (fDropRect.IsValid())
868 			Invalidate(fDropRect);
869 		fDropRect = r;
870 		if (fDropRect.IsValid())
871 			Invalidate(fDropRect);
872 	}
873 }
874 
875 // _SetDropIndex
876 void
877 DragSortableListView::_SetDropIndex(int32 index)
878 {
879 	if (fDropIndex != index) {
880 		fDropIndex = index;
881 		if (fDropIndex >= 0) {
882 			int32 count = CountItems();
883 			if (fDropIndex == count) {
884 				BRect r;
885 				if (ItemAt(count - 1)) {
886 					r = ItemFrame(count - 1);
887 					r.top = r.bottom;
888 					r.bottom = r.top + 1.0;
889 				} else {
890 					r = Bounds();
891 					r.bottom--;	// compensate for scrollbars moved slightly out of window
892 				}
893 				_SetDropAnticipationRect(r);
894 			} else {
895 				BRect r = ItemFrame(fDropIndex);
896 				r.top--;
897 				r.bottom = r.top + 1.0;
898 				_SetDropAnticipationRect(r);
899 			}
900 		}
901 	}
902 }
903 
904 // _RemoveDropAnticipationRect
905 void
906 DragSortableListView::_RemoveDropAnticipationRect()
907 {
908 	_SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0));
909 //	_SetDropIndex(-1);
910 }
911 
912 // _SetDragMessage
913 void
914 DragSortableListView::_SetDragMessage(const BMessage* message)
915 {
916 	if (message)
917 		fDragMessageCopy = *message;
918 	else
919 		fDragMessageCopy.what = 0;
920 }
921 
922 // #pragma mark - SimpleListView
923 
924 // SimpleListView class
925 SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage)
926 	: DragSortableListView(frame, "playlist listview",
927 						   B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL,
928 						   B_WILL_DRAW | B_NAVIGABLE
929 						   | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
930 	  fSelectionChangeMessage(selectionChangeMessage)
931 {
932 }
933 
934 // SimpleListView class
935 SimpleListView::SimpleListView(BRect frame, const char* name,
936 							   BMessage* selectionChangeMessage,
937 							   list_view_type type,
938 							   uint32 resizingMode, uint32 flags)
939 	: DragSortableListView(frame, name, type, resizingMode, flags),
940 	  fSelectionChangeMessage(selectionChangeMessage)
941 {
942 }
943 
944 // destructor
945 SimpleListView::~SimpleListView()
946 {
947 	delete fSelectionChangeMessage;
948 }
949 
950 #ifdef LIB_LAYOUT
951 // layoutprefs
952 minimax
953 SimpleListView::layoutprefs()
954 {
955 	mpm.mini.x = 30.0;
956 	mpm.maxi.x = 10000.0;
957 	mpm.mini.y = 50.0;
958 	mpm.maxi.y = 10000.0;
959 	mpm.weight = 1.0;
960 	return mpm;
961 }
962 
963 // layout
964 BRect
965 SimpleListView::layout(BRect frame)
966 {
967 	MoveTo(frame.LeftTop());
968 	ResizeTo(frame.Width(), frame.Height());
969 	return Frame();
970 }
971 #endif // LIB_LAYOUT
972 
973 // DetachedFromWindow
974 void
975 SimpleListView::DetachedFromWindow()
976 {
977 	DragSortableListView::DetachedFromWindow();
978 	_MakeEmpty();
979 }
980 
981 // MessageReceived
982 void
983 SimpleListView::MessageReceived(BMessage* message)
984 {
985 	switch (message->what) {
986 		default:
987 			DragSortableListView::MessageReceived(message);
988 			break;
989 	}
990 }
991 
992 // CloneItem
993 BListItem*
994 SimpleListView::CloneItem(int32 atIndex) const
995 {
996 	BListItem* clone = NULL;
997 	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(atIndex)))
998 		clone = new SimpleItem(item->Text());
999 	return clone;
1000 }
1001 
1002 // DrawListItem
1003 void
1004 SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const
1005 {
1006 	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(index))) {
1007 		uint32 flags  = FLAGS_NONE;
1008 		if (index == fFocusedIndex)
1009 			flags |= FLAGS_FOCUSED;
1010 		if (index % 2)
1011 			flags |= FLAGS_TINTED_LINE;
1012 		item->Draw(owner, frame, flags);
1013 	}
1014 }
1015 
1016 // MakeDragMessage
1017 void
1018 SimpleListView::MakeDragMessage(BMessage* message) const
1019 {
1020 	if (message) {
1021 		message->AddPointer("list",
1022 			(void*)dynamic_cast<const DragSortableListView*>(this));
1023 		int32 index;
1024 		for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++)
1025 			message->AddInt32("index", index);
1026 	}
1027 }
1028 
1029 // _MakeEmpty
1030 void
1031 SimpleListView::_MakeEmpty()
1032 {
1033 	// NOTE: BListView::MakeEmpty() uses ScrollTo()
1034 	// for which the object needs to be attached to
1035 	// a BWindow.... :-(
1036 	int32 count = CountItems();
1037 	for (int32 i = count - 1; i >= 0; i--)
1038 		delete RemoveItem(i);
1039 }
1040 
1041