xref: /haiku/src/apps/icon-o-matic/generic/gui/ListViews.cpp (revision 5e96d7d537fbec23bad4ae9b4c8e7b02e769f0c6)
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 		int32 count = CountItems();
328 		if (fDropIndex < 0 || fDropIndex > count)
329 			fDropIndex = count;
330 		HandleDropMessage(message, fDropIndex);
331 		fDropIndex = -1;
332 	} else {
333 		switch (message->what) {
334 			case MSG_TICK: {
335 				float scrollV = 0.0;
336 				BRect rect(Bounds());
337 				BPoint point;
338 				uint32 buttons;
339 				GetMouse(&point, &buttons, false);
340 				if (rect.Contains(point)) {
341 					// calculate the vertical scrolling offset
342 					float hotDist = rect.Height() * SCROLL_AREA;
343 					if (point.y > rect.bottom - hotDist)
344 						scrollV = hotDist - (rect.bottom - point.y);
345 					else if (point.y < rect.top + hotDist)
346 						scrollV = (point.y - rect.top) - hotDist;
347 				}
348 				// scroll
349 				if (scrollV != 0.0 && fScrollView) {
350 					if (BScrollBar* scrollBar = fScrollView->ScrollBar(B_VERTICAL)) {
351 						float value = scrollBar->Value();
352 						scrollBar->SetValue(scrollBar->Value() + scrollV);
353 						if (scrollBar->Value() != value) {
354 							// update mouse position
355 							uint32 buttons;
356 							BPoint point;
357 							GetMouse(&point, &buttons, false);
358 							uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
359 							MouseMoved(point, transit, &fDragMessageCopy);
360 						}
361 					}
362 				}
363 				break;
364 			}
365 //			case B_MODIFIERS_CHANGED:
366 //				ModifiersChanged();
367 //				break;
368 			case B_MOUSE_WHEEL_CHANGED: {
369 				BListView::MessageReceived(message);
370 				BPoint point;
371 				uint32 buttons;
372 				GetMouse(&point, &buttons, false);
373 				uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
374 				MouseMoved(point, transit, &fDragMessageCopy);
375 				break;
376 			}
377 			default:
378 				BListView::MessageReceived(message);
379 				break;
380 		}
381 	}
382 }
383 
384 // KeyDown
385 void
386 DragSortableListView::KeyDown(const char* bytes, int32 numBytes)
387 {
388 	if (numBytes < 1)
389 		return;
390 
391 	if ((bytes[0] == B_BACKSPACE) || (bytes[0] == B_DELETE))
392 		RemoveSelected();
393 
394 	BListView::KeyDown(bytes, numBytes);
395 }
396 
397 // MouseDown
398 void
399 DragSortableListView::MouseDown(BPoint where)
400 {
401 	int32 clicks = 1;
402 	uint32 buttons = 0;
403 	Window()->CurrentMessage()->FindInt32("clicks", &clicks);
404 	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
405 	int32 clickedIndex = -1;
406 	for (int32 i = 0; BListItem* item = ItemAt(i); i++) {
407 		if (ItemFrame(i).Contains(where)) {
408 			if (clicks == 2) {
409 				// only do something if user clicked the same item twice
410 				if (fLastClickedItem == item)
411 					DoubleClicked(i);
412 			} else {
413 				// remember last clicked item
414 				fLastClickedItem = item;
415 			}
416 			clickedIndex = i;
417 			break;
418 		}
419 	}
420 	if (clickedIndex == -1)
421 		fLastClickedItem = NULL;
422 
423 	BListItem* item = ItemAt(clickedIndex);
424 	if (ListType() == B_MULTIPLE_SELECTION_LIST
425 		&& item && (buttons & B_SECONDARY_MOUSE_BUTTON)) {
426 		if (item->IsSelected())
427 			Deselect(clickedIndex);
428 		else
429 			Select(clickedIndex, true);
430 	} else {
431 		BListView::MouseDown(where);
432 	}
433 }
434 
435 // MouseMoved
436 void
437 DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg)
438 {
439 	if (msg && AcceptDragMessage(msg)) {
440 		switch (transit) {
441 			case B_ENTERED_VIEW:
442 			case B_INSIDE_VIEW: {
443 				// remember drag message
444 				// this is needed to react on modifier changes
445 				_SetDragMessage(msg);
446 				// set drop target through virtual function
447 				SetDropTargetRect(msg, where);
448 				// go into autoscrolling mode
449 				BRect r = Bounds();
450 				r.InsetBy(0.0, r.Height() * SCROLL_AREA);
451 				SetAutoScrolling(!r.Contains(where));
452 				break;
453 			}
454 			case B_EXITED_VIEW:
455 				// forget drag message
456 				_SetDragMessage(NULL);
457 				SetAutoScrolling(false);
458 				// fall through
459 			case B_OUTSIDE_VIEW:
460 				_RemoveDropAnticipationRect();
461 				break;
462 		}
463 	} else {
464 		_RemoveDropAnticipationRect();
465 		BListView::MouseMoved(where, transit, msg);
466 		_SetDragMessage(NULL);
467 		SetAutoScrolling(false);
468 
469 		BCursor cursor(B_HAND_CURSOR);
470 		SetViewCursor(&cursor, true);
471 	}
472 	fLastMousePos = where;
473 }
474 
475 // MouseUp
476 void
477 DragSortableListView::MouseUp(BPoint where)
478 {
479 	// remove drop mark
480 	_SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0));
481 	SetAutoScrolling(false);
482 	// be sure to forget drag message
483 	_SetDragMessage(NULL);
484 	BListView::MouseUp(where);
485 
486 	BCursor cursor(B_HAND_CURSOR);
487 	SetViewCursor(&cursor, true);
488 }
489 
490 // DrawItem
491 void
492 DragSortableListView::DrawItem(BListItem *item, BRect itemFrame, bool complete)
493 {
494 	DrawListItem(this, IndexOf(item), itemFrame);
495 /*	if (IsFocus()) {
496 		SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
497 		StrokeRect(Bounds());
498 	}*/
499 }
500 
501 // #pragma mark -
502 
503 // MouseWheelChanged
504 bool
505 DragSortableListView::MouseWheelChanged(float x, float y)
506 {
507 	BPoint where;
508 	uint32 buttons;
509 	GetMouse(&where, &buttons, false);
510 	if (Bounds().Contains(where))
511 		return true;
512 	else
513 		return false;
514 }
515 
516 // #pragma mark -
517 
518 // ObjectChanged
519 void
520 DragSortableListView::ObjectChanged(const Observable* object)
521 {
522 	if (object != fSelection || fModifyingSelection || fSyncingToSelection)
523 		return;
524 
525 //printf("%s - syncing start\n", Name());
526 	fSyncingToSelection = true;
527 
528 	// try to sync to Selection
529 	BList selectedItems;
530 
531 	int32 count = fSelection->CountSelected();
532 	for (int32 i = 0; i < count; i++) {
533 		int32 index = IndexOfSelectable(fSelection->SelectableAtFast(i));
534 		if (index >= 0) {
535 			BListItem* item = ItemAt(index);
536 			if (item && !selectedItems.HasItem((void*)item))
537 				selectedItems.AddItem((void*)item);
538 		}
539 	}
540 
541 	count = selectedItems.CountItems();
542 	if (count == 0) {
543 		if (CurrentSelection(0) >= 0)
544 			DeselectAll();
545 	} else {
546 		count = CountItems();
547 		for (int32 i = 0; i < count; i++) {
548 			BListItem* item = ItemAt(i);
549 			bool selected = selectedItems.RemoveItem((void*)item);
550 			if (item->IsSelected() != selected) {
551 				Select(i, true);
552 			}
553 		}
554 	}
555 
556 	fSyncingToSelection = false;
557 //printf("%s - done\n", Name());
558 }
559 
560 // #pragma mark -
561 
562 // SetDragCommand
563 void
564 DragSortableListView::SetDragCommand(uint32 command)
565 {
566 	fDragCommand = command;
567 }
568 
569 // ModifiersChaned
570 void
571 DragSortableListView::ModifiersChanged()
572 {
573 	SetDropTargetRect(&fDragMessageCopy, fLastMousePos);
574 }
575 
576 // SetItemFocused
577 void
578 DragSortableListView::SetItemFocused(int32 index)
579 {
580 	InvalidateItem(fFocusedIndex);
581 	InvalidateItem(index);
582 	fFocusedIndex = index;
583 }
584 
585 // AcceptDragMessage
586 bool
587 DragSortableListView::AcceptDragMessage(const BMessage* message) const
588 {
589 	return message->what == fDragCommand;
590 }
591 
592 // SetDropTargetRect
593 void
594 DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where)
595 
596 {
597 	if (AcceptDragMessage(message)) {
598 		bool copy = modifiers() & B_SHIFT_KEY;
599 		bool replaceAll = !message->HasPointer("list") && !copy;
600 		BRect r = Bounds();
601 		if (replaceAll) {
602 			r.bottom--;	// compensate for scrollbar offset
603 			_SetDropAnticipationRect(r);
604 			fDropIndex = -1;
605 		} else {
606 			// offset where by half of item height
607 			r = ItemFrame(0);
608 			where.y += r.Height() / 2.0;
609 
610 			int32 index = IndexOf(where);
611 			if (index < 0)
612 				index = CountItems();
613 			_SetDropIndex(index);
614 
615 			const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR;
616 			BCursor cursor(cursorData);
617 			SetViewCursor(&cursor, true);
618 		}
619 	}
620 }
621 
622 
623 bool
624 DragSortableListView::HandleDropMessage(const BMessage* message,
625 	int32 dropIndex)
626 {
627 	DragSortableListView *list = NULL;
628 	if (message->FindPointer("list", (void **)&list) != B_OK || list != this)
629 		return false;
630 
631 	BList items;
632 	int32 index;
633 	for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; i++) {
634 		BListItem* item = ItemAt(index);
635 		if (item != NULL)
636 			items.AddItem((void*)item);
637 	}
638 
639 	if (items.CountItems() == 0)
640 		return false;
641 
642 	if ((modifiers() & B_SHIFT_KEY) != 0)
643 		CopyItems(items, dropIndex);
644 	else
645 		MoveItems(items, dropIndex);
646 
647 	return true;
648 }
649 
650 // SetAutoScrolling
651 void
652 DragSortableListView::SetAutoScrolling(bool enable)
653 {
654 	if (fScrollPulse && enable)
655 		return;
656 	if (enable) {
657 		BMessenger messenger(this, Window());
658 		BMessage message(MSG_TICK);
659 		fScrollPulse = new BMessageRunner(messenger, &message, 40000LL);
660 	} else {
661 		delete fScrollPulse;
662 		fScrollPulse = NULL;
663 	}
664 }
665 
666 // DoesAutoScrolling
667 bool
668 DragSortableListView::DoesAutoScrolling() const
669 {
670 	return fScrollPulse;
671 }
672 
673 // ScrollTo
674 void
675 DragSortableListView::ScrollTo(int32 index)
676 {
677 	if (index < 0)
678 		index = 0;
679 	if (index >= CountItems())
680 		index = CountItems() - 1;
681 
682 	if (ItemAt(index)) {
683 		BRect itemFrame = ItemFrame(index);
684 		BRect bounds = Bounds();
685 		if (itemFrame.top < bounds.top) {
686 			ScrollTo(itemFrame.LeftTop());
687 		} else if (itemFrame.bottom > bounds.bottom) {
688 			ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height()));
689 		}
690 	}
691 }
692 
693 // MoveItems
694 void
695 DragSortableListView::MoveItems(BList& items, int32 index)
696 {
697 	DeselectAll();
698 	// we remove the items while we look at them, the insertion index is decreased
699 	// when the items index is lower, so that we insert at the right spot after
700 	// removal
701 	BList removedItems;
702 	int32 count = items.CountItems();
703 	for (int32 i = 0; i < count; i++) {
704 		BListItem* item = (BListItem*)items.ItemAt(i);
705 		int32 removeIndex = IndexOf(item);
706 		if (RemoveItem(item) && removedItems.AddItem((void*)item)) {
707 			if (removeIndex < index)
708 				index--;
709 		}
710 		// else ??? -> blow up
711 	}
712 	for (int32 i = 0;
713 		 BListItem* item = (BListItem*)removedItems.ItemAt(i); i++) {
714 		if (AddItem(item, index)) {
715 			// after we're done, the newly inserted items will be selected
716 			Select(index, true);
717 			// next items will be inserted after this one
718 			index++;
719 		} else
720 			delete item;
721 	}
722 }
723 
724 // CopyItems
725 void
726 DragSortableListView::CopyItems(BList& items, int32 index)
727 {
728 	DeselectAll();
729 	// by inserting the items after we copied all items first, we avoid
730 	// cloning an item we already inserted and messing everything up
731 	// in other words, don't touch the list before we know which items
732 	// need to be cloned
733 	BList clonedItems;
734 	int32 count = items.CountItems();
735 	for (int32 i = 0; i < count; i++) {
736 		BListItem* item = CloneItem(IndexOf((BListItem*)items.ItemAt(i)));
737 		if (item && !clonedItems.AddItem((void*)item))
738 			delete item;
739 	}
740 	for (int32 i = 0;
741 		 BListItem* item = (BListItem*)clonedItems.ItemAt(i); i++) {
742 		if (AddItem(item, index)) {
743 			// after we're done, the newly inserted items will be selected
744 			Select(index, true);
745 			// next items will be inserted after this one
746 			index++;
747 		} else
748 			delete item;
749 	}
750 }
751 
752 // RemoveItemList
753 void
754 DragSortableListView::RemoveItemList(BList& items)
755 {
756 	int32 count = items.CountItems();
757 	for (int32 i = 0; i < count; i++) {
758 		BListItem* item = (BListItem*)items.ItemAt(i);
759 		if (RemoveItem(item))
760 			delete item;
761 	}
762 }
763 
764 // RemoveSelected
765 void
766 DragSortableListView::RemoveSelected()
767 {
768 //	if (fFocusedIndex >= 0)
769 //		return;
770 
771 	BList items;
772 	for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++)
773 		items.AddItem((void*)item);
774 	RemoveItemList(items);
775 }
776 
777 // #pragma mark -
778 
779 // SetSelection
780 void
781 DragSortableListView::SetSelection(Selection* selection)
782 {
783 	if (fSelection == selection)
784 		return;
785 
786 	if (fSelection)
787 		fSelection->RemoveObserver(this);
788 
789 	fSelection = selection;
790 
791 	if (fSelection)
792 		fSelection->AddObserver(this);
793 }
794 
795 // IndexOfSelectable
796 int32
797 DragSortableListView::IndexOfSelectable(Selectable* selectable) const
798 {
799 	return -1;
800 }
801 
802 // SelectableFor
803 Selectable*
804 DragSortableListView::SelectableFor(BListItem* item) const
805 {
806 	return NULL;
807 }
808 
809 // SelectAll
810 void
811 DragSortableListView::SelectAll()
812 {
813 	Select(0, CountItems() - 1);
814 }
815 
816 // CountSelectedItems
817 int32
818 DragSortableListView::CountSelectedItems() const
819 {
820 	int32 count = 0;
821 	while (CurrentSelection(count) >= 0)
822 		count++;
823 	return count;
824 }
825 
826 // SelectionChanged
827 void
828 DragSortableListView::SelectionChanged()
829 {
830 //printf("%s::SelectionChanged()", typeid(*this).name());
831 	// modify global Selection
832 	if (!fSelection || fSyncingToSelection)
833 		return;
834 
835 	fModifyingSelection = true;
836 
837 	BList selectables;
838 	for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++) {
839 		Selectable* selectable = SelectableFor(item);
840 		if (selectable)
841 			selectables.AddItem((void*)selectable);
842 	}
843 
844 	AutoNotificationSuspender _(fSelection);
845 
846 	int32 count = selectables.CountItems();
847 	if (count == 0) {
848 //printf("  deselecting all\n");
849 		if (!fSyncingToSelection)
850 			fSelection->DeselectAll();
851 	} else {
852 //printf("  selecting %ld items\n", count);
853 		for (int32 i = 0; i < count; i++) {
854 			Selectable* selectable = (Selectable*)selectables.ItemAtFast(i);
855 			fSelection->Select(selectable, i > 0);
856 		}
857 	}
858 
859 	fModifyingSelection = false;
860 }
861 
862 // #pragma mark -
863 
864 // DeleteItem
865 bool
866 DragSortableListView::DeleteItem(int32 index)
867 {
868 	BListItem* item = ItemAt(index);
869 	if (item && RemoveItem(item)) {
870 		delete item;
871 		return true;
872 	}
873 	return false;
874 }
875 
876 // _SetDropAnticipationRect
877 void
878 DragSortableListView::_SetDropAnticipationRect(BRect r)
879 {
880 	if (fDropRect != r) {
881 		if (fDropRect.IsValid())
882 			Invalidate(fDropRect);
883 		fDropRect = r;
884 		if (fDropRect.IsValid())
885 			Invalidate(fDropRect);
886 	}
887 }
888 
889 // _SetDropIndex
890 void
891 DragSortableListView::_SetDropIndex(int32 index)
892 {
893 	if (fDropIndex != index) {
894 		fDropIndex = index;
895 		if (fDropIndex >= 0) {
896 			int32 count = CountItems();
897 			if (fDropIndex == count) {
898 				BRect r;
899 				if (ItemAt(count - 1)) {
900 					r = ItemFrame(count - 1);
901 					r.top = r.bottom;
902 					r.bottom = r.top + 1.0;
903 				} else {
904 					r = Bounds();
905 					r.bottom--;	// compensate for scrollbars moved slightly out of window
906 				}
907 				_SetDropAnticipationRect(r);
908 			} else {
909 				BRect r = ItemFrame(fDropIndex);
910 				r.top--;
911 				r.bottom = r.top + 1.0;
912 				_SetDropAnticipationRect(r);
913 			}
914 		}
915 	}
916 }
917 
918 // _RemoveDropAnticipationRect
919 void
920 DragSortableListView::_RemoveDropAnticipationRect()
921 {
922 	_SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0));
923 //	_SetDropIndex(-1);
924 }
925 
926 // _SetDragMessage
927 void
928 DragSortableListView::_SetDragMessage(const BMessage* message)
929 {
930 	if (message)
931 		fDragMessageCopy = *message;
932 	else
933 		fDragMessageCopy.what = 0;
934 }
935 
936 // #pragma mark - SimpleListView
937 
938 // SimpleListView class
939 SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage)
940 	: DragSortableListView(frame, "playlist listview",
941 						   B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL,
942 						   B_WILL_DRAW | B_NAVIGABLE
943 						   | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
944 	  fSelectionChangeMessage(selectionChangeMessage)
945 {
946 }
947 
948 // SimpleListView class
949 SimpleListView::SimpleListView(BRect frame, const char* name,
950 							   BMessage* selectionChangeMessage,
951 							   list_view_type type,
952 							   uint32 resizingMode, uint32 flags)
953 	: DragSortableListView(frame, name, type, resizingMode, flags),
954 	  fSelectionChangeMessage(selectionChangeMessage)
955 {
956 }
957 
958 // destructor
959 SimpleListView::~SimpleListView()
960 {
961 	delete fSelectionChangeMessage;
962 }
963 
964 #ifdef LIB_LAYOUT
965 // layoutprefs
966 minimax
967 SimpleListView::layoutprefs()
968 {
969 	mpm.mini.x = 30.0;
970 	mpm.maxi.x = 10000.0;
971 	mpm.mini.y = 50.0;
972 	mpm.maxi.y = 10000.0;
973 	mpm.weight = 1.0;
974 	return mpm;
975 }
976 
977 // layout
978 BRect
979 SimpleListView::layout(BRect frame)
980 {
981 	MoveTo(frame.LeftTop());
982 	ResizeTo(frame.Width(), frame.Height());
983 	return Frame();
984 }
985 #endif // LIB_LAYOUT
986 
987 // DetachedFromWindow
988 void
989 SimpleListView::DetachedFromWindow()
990 {
991 	DragSortableListView::DetachedFromWindow();
992 	_MakeEmpty();
993 }
994 
995 // MessageReceived
996 void
997 SimpleListView::MessageReceived(BMessage* message)
998 {
999 	switch (message->what) {
1000 		default:
1001 			DragSortableListView::MessageReceived(message);
1002 			break;
1003 	}
1004 }
1005 
1006 // CloneItem
1007 BListItem*
1008 SimpleListView::CloneItem(int32 atIndex) const
1009 {
1010 	BListItem* clone = NULL;
1011 	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(atIndex)))
1012 		clone = new SimpleItem(item->Text());
1013 	return clone;
1014 }
1015 
1016 // DrawListItem
1017 void
1018 SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const
1019 {
1020 	if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(index))) {
1021 		uint32 flags  = FLAGS_NONE;
1022 		if (index == fFocusedIndex)
1023 			flags |= FLAGS_FOCUSED;
1024 		if (index % 2)
1025 			flags |= FLAGS_TINTED_LINE;
1026 		item->Draw(owner, frame, flags);
1027 	}
1028 }
1029 
1030 // MakeDragMessage
1031 void
1032 SimpleListView::MakeDragMessage(BMessage* message) const
1033 {
1034 	if (message) {
1035 		message->AddPointer("list",
1036 			(void*)dynamic_cast<const DragSortableListView*>(this));
1037 		int32 index;
1038 		for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++)
1039 			message->AddInt32("index", index);
1040 	}
1041 }
1042 
1043 // _MakeEmpty
1044 void
1045 SimpleListView::_MakeEmpty()
1046 {
1047 	// NOTE: BListView::MakeEmpty() uses ScrollTo()
1048 	// for which the object needs to be attached to
1049 	// a BWindow.... :-(
1050 	int32 count = CountItems();
1051 	for (int32 i = count - 1; i >= 0; i--)
1052 		delete RemoveItem(i);
1053 }
1054 
1055