1/*
2 * Copyright 2006-2007, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan Aßmus <superstippi@gmx.de>
7 */
8#include <stdio.h>
9#include <malloc.h>
10#include <new>
11
12#include <Bitmap.h>
13#include <Cursor.h>
14#include <Entry.h>
15#include <MessageRunner.h>
16#include <Messenger.h>
17#include <ScrollBar.h>
18#include <ScrollView.h>
19#include <String.h>
20#include <Window.h>
21
22#include "ListViews.h"
23
24const unsigned char kCopyCursor[] = { 16, 1, 1, 1,
25	0x00, 0x00, 0x70, 0x00, 0x48, 0x00, 0x48, 0x00,
26	0x27, 0xc0, 0x24, 0xb8, 0x12, 0x54, 0x10, 0x02,
27	0x79, 0xe2, 0x99, 0x22, 0x85, 0x7a, 0x61, 0x4a,
28	0x19, 0xca, 0x04, 0x4a, 0x02, 0x78, 0x00, 0x00,
29
30	0x00, 0x00, 0x70, 0x00, 0x78, 0x00, 0x78, 0x00,
31	0x3f, 0xc0, 0x3f, 0xf8, 0x1f, 0xfc, 0x1f, 0xfe,
32	0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe,
33	0x1f, 0xfe, 0x07, 0xfe, 0x03, 0xf8, 0x00, 0x00 };
34
35#define MAX_DRAG_HEIGHT		200.0
36#define ALPHA				170
37#define TEXT_OFFSET			5.0
38
39enum {
40	MSG_TICK	= 'tick',
41};
42
43using std::nothrow;
44
45// SimpleItem class
46SimpleItem::SimpleItem( const char *name )
47	: BStringItem( name )
48{
49}
50
51SimpleItem::~SimpleItem()
52{
53}
54
55// SimpleItem::DrawItem
56void
57SimpleItem::Draw(BView *owner, BRect frame, uint32 flags)
58{
59	DrawBackground(owner, frame, flags);
60	// label
61	if (IsSelected())
62		owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
63	else
64		owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
65	font_height fh;
66	owner->GetFontHeight( &fh );
67	const char* text = Text();
68	BString truncatedString( text );
69	owner->TruncateString( &truncatedString, B_TRUNCATE_MIDDLE,
70						   frame.Width() - TEXT_OFFSET - 4.0 );
71	float height = frame.Height();
72	float textHeight = fh.ascent + fh.descent;
73	BPoint textPoint;
74	textPoint.x = frame.left + TEXT_OFFSET;
75	textPoint.y = frame.top
76				  + ceilf(height / 2.0 - textHeight / 2.0
77				  		  + fh.ascent);
78	owner->DrawString(truncatedString.String(), textPoint);
79}
80
81// SimpleItem::DrawBackground
82void
83SimpleItem::DrawBackground(BView *owner, BRect frame, uint32 flags)
84{
85	// stroke a blue frame around the item if it's focused
86	if (flags & FLAGS_FOCUSED) {
87		owner->SetLowColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
88		owner->StrokeRect(frame, B_SOLID_LOW);
89		frame.InsetBy(1.0, 1.0);
90	}
91	// figure out bg-color
92	rgb_color color = ui_color(B_LIST_BACKGROUND_COLOR);
93
94	if (IsSelected())
95		color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
96
97	if ( flags & FLAGS_TINTED_LINE )
98		color = tint_color(color, 1.06);
99	// background
100	owner->SetLowColor(color);
101	owner->FillRect(frame, B_SOLID_LOW);
102}
103
104// DragSortableListView class
105DragSortableListView::DragSortableListView(BRect frame, const char* name,
106										   list_view_type type, uint32 resizingMode,
107										   uint32 flags)
108	: BListView(frame, name, type, resizingMode, flags),
109	  fDropRect(0.0, 0.0, -1.0, -1.0),
110	  fScrollPulse(NULL),
111	  fDropIndex(-1),
112	  fLastClickedItem(NULL),
113	  fScrollView(NULL),
114	  fDragCommand(B_SIMPLE_DATA),
115	  fFocusedIndex(-1)
116{
117	SetViewColor(B_TRANSPARENT_32_BIT);
118}
119
120DragSortableListView::~DragSortableListView()
121{
122	delete fScrollPulse;
123}
124
125// AttachedToWindow
126void
127DragSortableListView::AttachedToWindow()
128{
129	BListView::AttachedToWindow();
130
131	// work arround a bug in BListView
132	BRect bounds = Bounds();
133	BListView::FrameResized(bounds.Width(), bounds.Height());
134}
135
136// DetachedFromWindow
137void
138DragSortableListView::DetachedFromWindow()
139{
140}
141
142// FrameResized
143void
144DragSortableListView::FrameResized(float width, float height)
145{
146	BListView::FrameResized(width, height);
147	Invalidate();
148}
149
150/*
151// MakeFocus
152void
153DragSortableListView::MakeFocus(bool focused)
154{
155	if (focused != IsFocus()) {
156		Invalidate();
157		BListView::MakeFocus(focused);
158	}
159}
160*/
161// Draw
162void
163DragSortableListView::Draw( BRect updateRect )
164{
165	int32 firstIndex = IndexOf(updateRect.LeftTop());
166	int32 lastIndex = IndexOf(updateRect.RightBottom());
167	if (firstIndex >= 0) {
168		if (lastIndex < firstIndex)
169			lastIndex = CountItems() - 1;
170		// update rect contains items
171		BRect r = updateRect;
172		for (int32 i = firstIndex; i <= lastIndex; i++) {
173			r = ItemFrame(i);
174			DrawListItem(this, i, r);
175		}
176		updateRect.top = r.bottom + 1.0;
177		if (updateRect.IsValid()) {
178			SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
179			FillRect(updateRect, B_SOLID_LOW);
180		}
181	} else {
182		SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
183		FillRect(updateRect, B_SOLID_LOW);
184	}
185	// drop anticipation indication
186	if (fDropRect.IsValid()) {
187		SetHighColor(255, 0, 0, 255);
188		StrokeRect(fDropRect);
189	}
190/*	// focus indication
191	if (IsFocus()) {
192		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
193		StrokeRect(Bounds());
194	}*/
195}
196
197// ScrollTo
198void
199DragSortableListView::ScrollTo(BPoint where)
200{
201	uint32 buttons;
202	BPoint point;
203	GetMouse(&point, &buttons, false);
204	uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
205	MouseMoved(point, transit, &fDragMessageCopy);
206	BListView::ScrollTo(where);
207}
208
209// TargetedByScrollView
210void
211DragSortableListView::TargetedByScrollView(BScrollView* scrollView)
212{
213	fScrollView = scrollView;
214	BListView::TargetedByScrollView(scrollView);
215}
216
217// InitiateDrag
218bool
219DragSortableListView::InitiateDrag( BPoint point, int32 index, bool )
220{
221	// supress drag&drop while an item is focused
222	if (fFocusedIndex >= 0)
223		return false;
224
225	bool success = false;
226	BListItem* item = ItemAt( CurrentSelection( 0 ) );
227	if ( !item ) {
228		// workarround a timing problem
229		Select( index );
230		item = ItemAt( index );
231	}
232	if ( item ) {
233		// create drag message
234		BMessage msg( fDragCommand );
235		MakeDragMessage( &msg );
236		// figure out drag rect
237		float width = Bounds().Width();
238		BRect dragRect(0.0, 0.0, width, -1.0);
239		// figure out, how many items fit into our bitmap
240		int32 numItems;
241		bool fade = false;
242		for (numItems = 0; BListItem* item = ItemAt( CurrentSelection( numItems ) ); numItems++) {
243			dragRect.bottom += ceilf( item->Height() ) + 1.0;
244			if ( dragRect.Height() > MAX_DRAG_HEIGHT ) {
245				fade = true;
246				dragRect.bottom = MAX_DRAG_HEIGHT;
247				numItems++;
248				break;
249			}
250		}
251		BBitmap* dragBitmap = new BBitmap( dragRect, B_RGB32, true );
252		if ( dragBitmap && dragBitmap->IsValid() ) {
253			if ( BView *v = new BView( dragBitmap->Bounds(), "helper", 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
314void
315DragSortableListView::WindowActivated( bool active )
316{
317	// workarround for buggy focus indication of BScrollView
318	if ( BView* view = Parent() )
319		view->Invalidate();
320}
321
322// MessageReceived
323void
324DragSortableListView::MessageReceived(BMessage* message)
325{
326	if (AcceptDragMessage(message)) {
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 indices;
334			int32 index;
335			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; i++)
336				indices.AddItem((void*)(addr_t)index);
337			if (indices.CountItems() > 0) {
338				if (modifiers() & B_SHIFT_KEY)
339					CopyItems(indices, fDropIndex);
340				else
341					MoveItems(indices, fDropIndex);
342			}
343			fDropIndex = -1;
344		}
345	} else {
346		switch (message->what) {
347			case MSG_TICK: {
348				float scrollV = 0.0;
349				BRect rect(Bounds());
350				BPoint point;
351				uint32 buttons;
352				GetMouse(&point, &buttons, false);
353				if (rect.Contains(point)) {
354					// calculate the vertical scrolling offset
355					float hotDist = rect.Height() * SCROLL_AREA;
356					if (point.y > rect.bottom - hotDist)
357						scrollV = hotDist - (rect.bottom - point.y);
358					else if (point.y < rect.top + hotDist)
359						scrollV = (point.y - rect.top) - hotDist;
360				}
361				// scroll
362				if (scrollV != 0.0 && fScrollView) {
363					if (BScrollBar* scrollBar = fScrollView->ScrollBar(B_VERTICAL)) {
364						float value = scrollBar->Value();
365						scrollBar->SetValue(scrollBar->Value() + scrollV);
366						if (scrollBar->Value() != value) {
367							// update mouse position
368							uint32 buttons;
369							BPoint point;
370							GetMouse(&point, &buttons, false);
371							uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
372							MouseMoved(point, transit, &fDragMessageCopy);
373						}
374					}
375				}
376				break;
377			}
378			case B_MODIFIERS_CHANGED:
379				ModifiersChanged();
380				break;
381			case B_MOUSE_WHEEL_CHANGED: {
382				BListView::MessageReceived( message );
383				BPoint point;
384				uint32 buttons;
385				GetMouse(&point, &buttons, false);
386				uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
387				MouseMoved(point, transit, &fDragMessageCopy);
388				break;
389			}
390			default:
391				BListView::MessageReceived( message );
392				break;
393		}
394	}
395}
396
397// KeyDown
398void
399DragSortableListView::KeyDown( const char* bytes, int32 numBytes )
400{
401	if ( numBytes < 1 )
402		return;
403
404	if ( ( bytes[0] == B_BACKSPACE ) || ( bytes[0] == B_DELETE ) )
405		RemoveSelected();
406
407	BListView::KeyDown( bytes, numBytes );
408}
409
410// MouseDown
411void
412DragSortableListView::MouseDown( BPoint where )
413{
414	int32 clicks = 1;
415	uint32 buttons = 0;
416	Window()->CurrentMessage()->FindInt32("clicks", &clicks);
417	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
418	int32 clickedIndex = -1;
419	for (int32 i = 0; BListItem* item = ItemAt(i); i++) {
420		if (ItemFrame(i).Contains(where)) {
421			if (clicks == 2) {
422				// only do something if user clicked the same item twice
423				if (fLastClickedItem == item)
424					DoubleClicked(i);
425			} else {
426				// remember last clicked item
427				fLastClickedItem = item;
428			}
429			clickedIndex = i;
430			break;
431		}
432	}
433	if (clickedIndex == -1)
434		fLastClickedItem = NULL;
435
436	BListItem* item = ItemAt(clickedIndex);
437	if (ListType() == B_MULTIPLE_SELECTION_LIST
438		&& item && (buttons & B_SECONDARY_MOUSE_BUTTON)) {
439		if (item->IsSelected())
440			Deselect(clickedIndex);
441		else
442			Select(clickedIndex, true);
443	} else {
444		BListView::MouseDown(where);
445	}
446}
447
448// MouseMoved
449void
450DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg)
451{
452	if (msg && AcceptDragMessage(msg)) {
453		switch (transit) {
454			case B_ENTERED_VIEW:
455			case B_INSIDE_VIEW: {
456				// remember drag message
457				// this is needed to react on modifier changes
458				_SetDragMessage(msg);
459				// set drop target through virtual function
460				SetDropTargetRect(msg, where);
461				// go into autoscrolling mode
462				BRect r = Bounds();
463				r.InsetBy(0.0, r.Height() * SCROLL_AREA);
464				SetAutoScrolling(!r.Contains(where));
465				break;
466			}
467			case B_EXITED_VIEW:
468				// forget drag message
469				_SetDragMessage(NULL);
470				SetAutoScrolling(false);
471				// fall through
472			case B_OUTSIDE_VIEW:
473				_RemoveDropAnticipationRect();
474				break;
475		}
476	} else {
477		_RemoveDropAnticipationRect();
478		BListView::MouseMoved(where, transit, msg);
479		_SetDragMessage(NULL);
480		SetAutoScrolling(false);
481
482		BCursor cursor(B_HAND_CURSOR);
483		SetViewCursor(&cursor, true);
484	}
485	fLastMousePos = where;
486}
487
488// MouseUp
489void
490DragSortableListView::MouseUp( BPoint where )
491{
492	// remove drop mark
493	_SetDropAnticipationRect( BRect( 0.0, 0.0, -1.0, -1.0 ) );
494	SetAutoScrolling(false);
495	// be sure to forget drag message
496	_SetDragMessage(NULL);
497	BListView::MouseUp( where );
498
499	BCursor cursor(B_HAND_CURSOR);
500	SetViewCursor(&cursor, true);
501}
502
503// DrawItem
504void
505DragSortableListView::DrawItem( BListItem *item, BRect itemFrame, bool complete )
506{
507	DrawListItem( this, IndexOf( item ), itemFrame );
508/*	if (IsFocus()) {
509		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
510		StrokeRect(Bounds());
511	}*/
512}
513
514// MouseWheelChanged
515bool
516DragSortableListView::MouseWheelChanged(float x, float y)
517{
518	BPoint where;
519	uint32 buttons;
520	GetMouse(&where, &buttons, false);
521	if (Bounds().Contains(where))
522		return true;
523	else
524		return false;
525}
526
527// SetDragCommand
528void
529DragSortableListView::SetDragCommand(uint32 command)
530{
531	fDragCommand = command;
532}
533
534// ModifiersChaned
535void
536DragSortableListView::ModifiersChanged()
537{
538	SetDropTargetRect(&fDragMessageCopy, fLastMousePos);
539}
540
541// SetItemFocused
542void
543DragSortableListView::SetItemFocused(int32 index)
544{
545	InvalidateItem(fFocusedIndex);
546	InvalidateItem(index);
547	fFocusedIndex = index;
548}
549
550// AcceptDragMessage
551bool
552DragSortableListView::AcceptDragMessage(const BMessage* message) const
553{
554	return message->what == fDragCommand;
555}
556
557// SetDropTargetRect
558void
559DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where)
560
561{
562	if (AcceptDragMessage(message)) {
563		bool copy = modifiers() & B_SHIFT_KEY;
564		bool replaceAll = !message->HasPointer("list") && copy;
565			// when dragging something in from ie Tracker, the
566			// user has to hold shift down to replace everything
567			// (opposite meaning of the normal shift behaviour)
568		BRect r = Bounds();
569		if (replaceAll) {
570			r.bottom--;	// compensate for scrollbar offset
571			_SetDropAnticipationRect(r);
572			fDropIndex = -1;
573		} else {
574			// offset where by half of item height
575			r = ItemFrame(0);
576			where.y += r.Height() / 2.0;
577
578			int32 index = IndexOf(where);
579			if (index < 0)
580				index = CountItems();
581			_SetDropIndex(index);
582
583			const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR;
584			BCursor cursor(cursorData);
585			SetViewCursor(&cursor, true);
586		}
587	}
588}
589
590// SetAutoScrolling
591void
592DragSortableListView::SetAutoScrolling(bool enable)
593{
594	if (fScrollPulse && enable)
595		return;
596	if (enable) {
597		BMessenger messenger(this, Window());
598		BMessage message(MSG_TICK);
599		fScrollPulse = new BMessageRunner(messenger, &message, 40000LL);
600	} else {
601		delete fScrollPulse;
602		fScrollPulse = NULL;
603	}
604}
605
606// DoesAutoScrolling
607bool
608DragSortableListView::DoesAutoScrolling() const
609{
610	return fScrollPulse;
611}
612
613// ScrollTo
614void
615DragSortableListView::ScrollTo(int32 index)
616{
617	if (index < 0)
618		index = 0;
619	if (index >= CountItems())
620		index = CountItems() - 1;
621
622	if (ItemAt(index)) {
623		BRect itemFrame = ItemFrame(index);
624		BRect bounds = Bounds();
625		if (itemFrame.top < bounds.top) {
626			ScrollTo(itemFrame.LeftTop());
627		} else if (itemFrame.bottom > bounds.bottom) {
628			ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height()));
629		}
630	}
631}
632
633// MoveItems
634void
635DragSortableListView::MoveItems(const BList& indices, int32 index)
636{
637	DeselectAll();
638	// we remove the items while we look at them, the insertion index is decreased
639	// when the items index is lower, so that we insert at the right spot after
640	// removal
641	BList removedItems;
642	int32 count = indices.CountItems();
643	for (int32 i = 0; i < count; i++) {
644		int32 removeIndex = (int32)(addr_t)indices.ItemAtFast(i) - i;
645		BListItem* item = RemoveItem(removeIndex);
646		if (item && removedItems.AddItem((void*)item)) {
647			if (removeIndex < index)
648				index--;
649		}
650		// else ??? -> blow up
651	}
652	count = removedItems.CountItems();
653	for (int32 i = 0; i < count; i++) {
654		BListItem* item = (BListItem*)removedItems.ItemAtFast(i);
655		if (AddItem(item, index)) {
656			// after we're done, the newly inserted items will be selected
657			Select(index, true);
658			// next items will be inserted after this one
659			index++;
660		} else
661			delete item;
662	}
663}
664
665// CopyItems
666void
667DragSortableListView::CopyItems(const BList& indices, int32 toIndex)
668{
669	DeselectAll();
670	// by inserting the items after we copied all items first, we avoid
671	// cloning an item we already inserted and messing everything up
672	// in other words, don't touch the list before we know which items
673	// need to be cloned
674	BList clonedItems;
675	int32 count = indices.CountItems();
676	for (int32 i = 0; i < count; i++) {
677		int32 index = (int32)(addr_t)indices.ItemAtFast(i);
678		BListItem* item = CloneItem(index);
679		if (item && !clonedItems.AddItem((void*)(addr_t)item))
680			delete item;
681	}
682	count = clonedItems.CountItems();
683	for (int32 i = 0; i < count; i++) {
684		BListItem* item = (BListItem*)clonedItems.ItemAtFast(i);
685		if (AddItem(item, toIndex)) {
686			// after we're done, the newly inserted items will be selected
687			Select(toIndex, true);
688			// next items will be inserted after this one
689			toIndex++;
690		} else
691			delete item;
692	}
693}
694
695// RemoveItemList
696void
697DragSortableListView::RemoveItemList(const BList& indices)
698{
699	int32 count = indices.CountItems();
700	for (int32 i = 0; i < count; i++) {
701		int32 index = (int32)(addr_t)indices.ItemAtFast(i) - i;
702		delete RemoveItem(index);
703	}
704}
705
706// GetSelectedItems
707void
708DragSortableListView::GetSelectedItems(BList& indices)
709{
710	for (int32 i = 0; true; i++) {
711		int32 index = CurrentSelection(i);
712		if (index < 0)
713			break;
714		if (!indices.AddItem((void*)(addr_t)index))
715			break;
716	}
717}
718
719// RemoveSelected
720void
721DragSortableListView::RemoveSelected()
722{
723	BList indices;
724	GetSelectedItems(indices);
725	int32 index = CurrentSelection() - 1;
726
727	DeselectAll();
728
729	if (indices.CountItems() > 0)
730		RemoveItemList(indices);
731
732	if (CountItems() > 0) {
733		if (index < 0)
734			index = 0;
735
736		Select(index);
737	}
738}
739
740// RemoveAll
741void
742DragSortableListView::RemoveAll()
743{
744	BList indices;
745	int32 count = CountItems();
746	for (int32 i = 0; i < count; i++) {
747		if (!indices.AddItem((void*)(addr_t)i))
748			break;
749	}
750
751	if (indices.CountItems() > 0)
752		RemoveItemList(indices);
753}
754
755// CountSelectedItems
756int32
757DragSortableListView::CountSelectedItems() const
758{
759	int32 count = 0;
760	while (CurrentSelection(count) >= 0)
761		count++;
762	return count;
763}
764
765// SelectAll
766void
767DragSortableListView::SelectAll()
768{
769	Select(0, CountItems() - 1);
770}
771
772// DeleteItem
773bool
774DragSortableListView::DeleteItem(int32 index)
775{
776	BListItem* item = ItemAt(index);
777	if (item && RemoveItem(item)) {
778		delete item;
779		return true;
780	}
781	return false;
782}
783
784// _SetDropAnticipationRect
785void
786DragSortableListView::_SetDropAnticipationRect(BRect r)
787{
788	if (fDropRect != r) {
789		if (fDropRect.IsValid())
790			Invalidate(fDropRect);
791		fDropRect = r;
792		if (fDropRect.IsValid())
793			Invalidate(fDropRect);
794	}
795}
796
797// _SetDropIndex
798void
799DragSortableListView::_SetDropIndex(int32 index)
800{
801	if (fDropIndex != index) {
802		fDropIndex = index;
803		if (fDropIndex >= 0) {
804			int32 count = CountItems();
805			if (fDropIndex == count) {
806				BRect r;
807				if (ItemAt(count - 1)) {
808					r = ItemFrame(count - 1);
809					r.top = r.bottom;
810					r.bottom = r.top + 1.0;
811				} else {
812					r = Bounds();
813					r.bottom--;	// compensate for scrollbars moved slightly out of window
814				}
815				_SetDropAnticipationRect(r);
816			} else {
817				BRect r = ItemFrame(fDropIndex);
818				r.top--;
819				r.bottom = r.top + 1.0;
820				_SetDropAnticipationRect(r);
821			}
822		}
823	}
824}
825
826// _RemoveDropAnticipationRect
827void
828DragSortableListView::_RemoveDropAnticipationRect()
829{
830	_SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0));
831	_SetDropIndex(-1);
832}
833
834// _SetDragMessage
835void
836DragSortableListView::_SetDragMessage(const BMessage* message)
837{
838	if (message)
839		fDragMessageCopy = *message;
840	else
841		fDragMessageCopy.what = 0;
842}
843
844
845
846// SimpleListView class
847SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage)
848	: DragSortableListView(frame, "playlist listview",
849						   B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL,
850						   B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS),
851	  fSelectionChangeMessage(selectionChangeMessage)
852{
853}
854
855// SimpleListView class
856SimpleListView::SimpleListView(BRect frame, const char* name,
857							   BMessage* selectionChangeMessage,
858							   list_view_type type,
859							   uint32 resizingMode, uint32 flags)
860	: DragSortableListView(frame, name, type, resizingMode, flags),
861	  fSelectionChangeMessage(selectionChangeMessage)
862{
863}
864
865// destructor
866SimpleListView::~SimpleListView()
867{
868	delete fSelectionChangeMessage;
869}
870
871// MessageReceived
872void
873SimpleListView::MessageReceived( BMessage* message)
874{
875	switch (message->what) {
876		default:
877			DragSortableListView::MessageReceived(message);
878			break;
879	}
880}
881
882// SelectionChanged
883void
884SimpleListView::SelectionChanged()
885{
886	BLooper* looper = Looper();
887	if (fSelectionChangeMessage && looper) {
888		BMessage message(*fSelectionChangeMessage);
889		looper->PostMessage(&message);
890	}
891}
892
893// CloneItem
894BListItem*
895SimpleListView::CloneItem(int32 atIndex) const
896{
897	BListItem* clone = NULL;
898	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(atIndex)))
899		clone = new SimpleItem(item->Text());
900	return clone;
901}
902
903// DrawListItem
904void
905SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const
906{
907	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(index))) {
908		uint32 flags  = FLAGS_NONE;
909		if (index == fFocusedIndex)
910			flags |= FLAGS_FOCUSED;
911		if (index % 2)
912			flags |= FLAGS_TINTED_LINE;
913		item->Draw(owner, frame, flags);
914	}
915}
916
917// MakeDragMessage
918void
919SimpleListView::MakeDragMessage(BMessage* message) const
920{
921	if (message) {
922		message->AddPointer( "list", (void*)dynamic_cast<const DragSortableListView*>(this));
923		int32 index;
924		for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++)
925			message->AddInt32( "index", index );
926	}
927}
928
929