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