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