xref: /haiku/src/kits/interface/ListView.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright (c) 2001-2006, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Ulrich Wimboeck
7  *		Marc Flerackers (mflerackers@androme.be)
8  *		Stephan Assmus <superstippi@gmx.de>
9  *		Axel Dörfler, axeld@pinc-software.de
10  */
11 
12 
13 #include <ListView.h>
14 
15 #include <Autolock.h>
16 #include <PropertyInfo.h>
17 #include <ScrollBar.h>
18 #include <ScrollView.h>
19 #include <Window.h>
20 
21 #include <stdio.h>
22 
23 
24 struct track_data {
25 	BPoint		drag_start;
26 	int32		item_index;
27 	bool		was_selected;
28 	bool		try_drag;
29 };
30 
31 static property_info sProperties[] = {
32 	{ "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
33 		"Returns the number of BListItems currently in the list." },
34 	{ "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER,
35 		B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
36 		"Select and invoke the specified items, first removing any existing selection." },
37 	{ "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
38 		"Returns int32 count of items in the selection." },
39 	{ "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
40 		"Invoke items in selection." },
41 	{ "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
42 		"Returns int32 indices of all items in the selection." },
43 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER,
44 		B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
45 		"Extends current selection or deselects specified items. Boolean field \"data\" "
46 		"chooses selection or deselection." },
47 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
48 		"Select or deselect all items in the selection. Boolean field \"data\" chooses "
49 		"selection or deselection." },
50 };
51 
52 
53 BListView::BListView(BRect frame, const char* name, list_view_type type,
54 		uint32 resizingMode, uint32 flags)
55 	: BView(frame, name, resizingMode, flags)
56 {
57 	_InitObject(type);
58 }
59 
60 
61 BListView::BListView(BMessage* archive)
62 	: BView(archive)
63 {
64 	int32 listType;
65 
66 	archive->FindInt32("_lv_type", &listType);
67 	fListType = (list_view_type)listType;
68 
69 	fFirstSelected = -1;
70 	fLastSelected = -1;
71 	fAnchorIndex = -1;
72 
73 	fSelectMessage = NULL;
74 	fScrollView = NULL;
75 	fTrack = NULL;
76 
77 	fWidth = Bounds().Width();
78 
79 	int32 i = 0;
80 	BMessage subData;
81 
82 	while (archive->FindMessage("_l_items", i++, &subData)) {
83 		BArchivable *object = instantiate_object(&subData);
84 		if (!object)
85 			continue;
86 
87 		BListItem *item = dynamic_cast<BListItem*>(object);
88 		if (!item)
89 			continue;
90 
91 		AddItem(item);
92 	}
93 
94 	if (archive->HasMessage("_msg")) {
95 		BMessage *invokationMessage = new BMessage;
96 
97 		archive->FindMessage("_msg", invokationMessage);
98 		SetInvocationMessage(invokationMessage);
99 	}
100 
101 	if (archive->HasMessage("_2nd_msg")) {
102 		BMessage *selectionMessage = new BMessage;
103 
104 		archive->FindMessage("_2nd_msg", selectionMessage);
105 		SetSelectionMessage(selectionMessage);
106 	}
107 }
108 
109 
110 BListView::~BListView()
111 {
112 	SetSelectionMessage(NULL);
113 	delete fSelectMessage;
114 }
115 
116 
117 BArchivable *
118 BListView::Instantiate(BMessage *archive)
119 {
120 	if (validate_instantiation(archive, "BListView"))
121 		return new BListView(archive);
122 
123 	return NULL;
124 }
125 
126 
127 status_t
128 BListView::Archive(BMessage *archive, bool deep) const
129 {
130 	status_t status = BView::Archive(archive, deep);
131 	if (status < B_OK)
132 		return status;
133 
134 	status = archive->AddInt32("_lv_type", fListType);
135 	if (status == B_OK && deep) {
136 		BListItem *item;
137 		int32 i = 0;
138 
139 		while ((item = ItemAt(i++))) {
140 			BMessage subData;
141 			status = item->Archive(&subData, true);
142 			if (status >= B_OK)
143 				status = archive->AddMessage("_l_items", &subData);
144 
145 			if (status < B_OK)
146 				break;
147 		}
148 	}
149 
150 	if (status >= B_OK && InvocationMessage() != NULL)
151 		status = archive->AddMessage("_msg", InvocationMessage());
152 
153 	if (status == B_OK && fSelectMessage != NULL)
154 		status = archive->AddMessage("_2nd_msg", fSelectMessage);
155 
156 	return status;
157 }
158 
159 
160 void
161 BListView::Draw(BRect updateRect)
162 {
163 	for (int i = 0; i < CountItems(); i++) {
164 		BRect itemFrame = ItemFrame(i);
165 
166 		if (itemFrame.Intersects(updateRect))
167 			DrawItem(((BListItem*)ItemAt(i)), itemFrame);
168 	}
169 }
170 
171 
172 void
173 BListView::MessageReceived(BMessage* msg)
174 {
175 	switch (msg->what) {
176 		case B_COUNT_PROPERTIES:
177 		case B_EXECUTE_PROPERTY:
178 		case B_GET_PROPERTY:
179 		case B_SET_PROPERTY:
180 		{
181 			BPropertyInfo propInfo(sProperties);
182 			BMessage specifier;
183 			const char *property;
184 
185 			if (msg->GetCurrentSpecifier(NULL, &specifier) != B_OK
186 				|| specifier.FindString("property", &property) != B_OK)
187 				return;
188 
189 			switch (propInfo.FindMatch(msg, 0, &specifier, msg->what, property)) {
190 				case B_ERROR:
191 					BView::MessageReceived(msg);
192 					break;
193 
194 				case 0:
195 				{
196 					BMessage reply(B_REPLY);
197 					reply.AddInt32("result", CountItems());
198 					reply.AddInt32("error", B_OK);
199 
200 					msg->SendReply(&reply);
201 					break;
202 				}
203 
204 				case 1:
205 					break;
206 
207 				case 2:
208 				{
209 					int32 count = 0;
210 
211 					for (int32 i = 0; i < CountItems(); i++) {
212 						if (ItemAt(i)->IsSelected())
213 							count++;
214 					}
215 
216 					BMessage reply(B_REPLY);
217 					reply.AddInt32("result", count);
218 					reply.AddInt32("error", B_OK);
219 
220 					msg->SendReply(&reply);
221 					break;
222 				}
223 
224 				case 3:
225 					break;
226 
227 				case 4:
228 				{
229 					BMessage reply (B_REPLY);
230 
231 					for (int32 i = 0; i < CountItems(); i++) {
232 						if (ItemAt(i)->IsSelected())
233 							reply.AddInt32("result", i);
234 					}
235 
236 					reply.AddInt32("error", B_OK);
237 
238 					msg->SendReply(&reply);
239 					break;
240 				}
241 
242 				case 5:
243 					break;
244 
245 				case 6:
246 				{
247 					BMessage reply(B_REPLY);
248 
249 					bool select;
250 					if (msg->FindBool("data", &select) == B_OK && select)
251 						Select(0, CountItems() - 1, false);
252 					else
253 						DeselectAll();
254 
255 					reply.AddInt32("error", B_OK);
256 
257 					msg->SendReply(&reply);
258 					break;
259 				}
260 			}
261 			break;
262 		}
263 
264 		case B_SELECT_ALL:
265 			Select(0, CountItems() - 1, false);
266 			break;
267 
268 		default:
269 			BView::MessageReceived(msg);
270 	}
271 }
272 
273 
274 void
275 BListView::MouseDown(BPoint point)
276 {
277 	if (!IsFocus()) {
278 		MakeFocus();
279 		Sync();
280 		Window()->UpdateIfNeeded();
281 	}
282 
283 	BMessage *message = Looper()->CurrentMessage();
284 	int32 index = IndexOf(point);
285 
286 	// If the user double (or more) clicked within the current selection,
287 	// we don't change the selection but invoke the selection.
288 	int32 clicks;
289 	if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
290 		&& index >= fFirstSelected && index <= fLastSelected) {
291 		Invoke();
292 		return;
293 	}
294 
295 	int32 modifiers;
296 	message->FindInt32("modifiers", &modifiers);
297 
298 	fTrack->drag_start = point;
299 	fTrack->item_index = index;
300 	fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
301 	fTrack->try_drag = true;
302 
303 	if (index > -1) {
304 		if (fListType == B_MULTIPLE_SELECTION_LIST) {
305 			if (modifiers & B_CONTROL_KEY) {
306 				// select entire block
307 				// TODO: maybe review if we want it like in Tracker (anchor item)
308 				Select(min_c(index, fFirstSelected), max_c(index, fLastSelected));
309 			} else {
310 				if (modifiers & B_SHIFT_KEY) {
311 					// toggle selection state of clicked item (like in Tracker)
312 					// toggle selection state of clicked item
313 					if (ItemAt(index)->IsSelected())
314 						Deselect(index);
315 					else
316 						Select(index, true);
317 				} else {
318 					Select(index);
319 				}
320 			}
321 		} else {
322 			// toggle selection state of clicked item
323 			if ((modifiers & B_SHIFT_KEY) && ItemAt(index)->IsSelected())
324 				Deselect(index);
325 			else
326 				Select(index);
327 		}
328 	} else {
329 		if (!(modifiers & B_SHIFT_KEY))
330 			DeselectAll();
331 	}
332 }
333 
334 // MouseUp
335 void
336 BListView::MouseUp(BPoint pt)
337 {
338 	fTrack->item_index = -1;
339 	fTrack->try_drag = false;
340 }
341 
342 // MouseMoved
343 void
344 BListView::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
345 {
346 	if (fTrack->item_index == -1) {
347 		// mouse was not clicked above any item
348 		// or no mouse button pressed
349 		return;
350 	}
351 
352 	if (_TryInitiateDrag(pt))
353 		return;
354 }
355 
356 // KeyDown
357 void
358 BListView::KeyDown(const char *bytes, int32 numBytes)
359 {
360 	switch (bytes[0]) {
361 		case B_UP_ARROW:
362 		{
363 			if (fFirstSelected == -1)
364 				break;
365 
366 			bool extend = false;
367 
368 			if (fListType == B_MULTIPLE_SELECTION_LIST && (modifiers() & B_SHIFT_KEY))
369 				extend = true;
370 
371 			Select (fFirstSelected - 1, extend);
372 
373 			ScrollToSelection ();
374 
375 			break;
376 		}
377 		case B_DOWN_ARROW:
378 		{
379 			if (fFirstSelected == -1)
380 				break;
381 
382 			bool extend = false;
383 
384 			if (fListType == B_MULTIPLE_SELECTION_LIST && (modifiers() & B_SHIFT_KEY))
385 				extend = true;
386 
387 			Select (fLastSelected + 1, extend);
388 
389 			ScrollToSelection ();
390 
391 			break;
392 		}
393 		case B_HOME:
394 		{
395 			Select ( 0, fListType == B_MULTIPLE_SELECTION_LIST );
396 
397 			ScrollToSelection ();
398 
399 			break;
400 		}
401 		case B_END:
402 		{
403 			Select ( CountItems () - 1, fListType == B_MULTIPLE_SELECTION_LIST );
404 
405 			ScrollToSelection ();
406 
407 			break;
408 		}
409 		case B_RETURN:
410 		case B_SPACE:
411 		{
412 			Invoke ();
413 
414 			break;
415 		}
416 		default:
417 			BView::KeyDown ( bytes, numBytes );
418 	}
419 }
420 
421 // MakeFocus
422 void
423 BListView::MakeFocus(bool focused)
424 {
425 	if (IsFocus() == focused)
426 		return;
427 
428 	BView::MakeFocus(focused);
429 
430 	if (fScrollView)
431 		fScrollView->SetBorderHighlighted(focused);
432 }
433 
434 // FrameResized
435 void
436 BListView::FrameResized(float width, float height)
437 {
438 	fWidth = Bounds().right;
439 	_FixupScrollBar();
440 }
441 
442 // TargetedByScrollView
443 void
444 BListView::TargetedByScrollView(BScrollView *view)
445 {
446 	fScrollView = view;
447 }
448 
449 
450 void
451 BListView::ScrollTo(BPoint point)
452 {
453 	BView::ScrollTo(point);
454 	fWidth = Bounds().right;
455 }
456 
457 
458 bool
459 BListView::AddItem(BListItem *item, int32 index)
460 {
461 	if (!fList.AddItem(item, index))
462 		return false;
463 
464 	if (fFirstSelected != -1 && index <= fFirstSelected)
465 		fFirstSelected++;
466 
467 	if (fLastSelected != -1 && index <= fLastSelected)
468 		fLastSelected++;
469 
470 	if (Window()) {
471 		BFont font;
472 		GetFont(&font);
473 
474 		item->Update(this, &font);
475 
476 		_FixupScrollBar();
477 		_InvalidateFrom(index);
478 	}
479 
480 	return true;
481 }
482 
483 
484 bool
485 BListView::AddItem(BListItem* item)
486 {
487 	if (!fList.AddItem(item))
488 		return false;
489 
490 	// No need to adapt selection, as this item is the last in the list
491 
492 	if (Window()) {
493 		BFont font;
494 		GetFont(&font);
495 
496 		item->Update(this, &font);
497 
498 		_FixupScrollBar();
499 		InvalidateItem(CountItems() - 1);
500 	}
501 
502 	return true;
503 }
504 
505 // AddList
506 bool
507 BListView::AddList(BList* list, int32 index)
508 {
509 	if (!fList.AddList(list, index))
510 		return false;
511 
512 	int32 count = fList.CountItems();
513 
514 	if (fFirstSelected != -1 && index < fFirstSelected)
515 		fFirstSelected += count;
516 
517 	if (fLastSelected != -1 && index < fLastSelected)
518 		fLastSelected += count;
519 
520 	if (Window()) {
521 		BFont font;
522 		GetFont(&font);
523 
524 		int32 i = 0;
525 		while(BListItem *item = (BListItem*)list->ItemAt(i)) {
526 			item->Update(this, &font);
527 			i++;
528 		}
529 
530 		_FixupScrollBar();
531 		Invalidate(); // TODO
532 	}
533 
534 	return true;
535 }
536 
537 // AddList
538 bool
539 BListView::AddList(BList* list)
540 {
541 	return AddList(list, CountItems());
542 }
543 
544 // RemoveItem
545 BListItem*
546 BListView::RemoveItem(int32 index)
547 {
548 	BListItem *item = ItemAt(index);
549 	if (!item)
550 		return NULL;
551 
552 	if (item->IsSelected())
553 		Deselect(index);
554 
555 	if (!fList.RemoveItem(item))
556 		return NULL;
557 
558 	if (fFirstSelected != -1 && index < fFirstSelected)
559 		fFirstSelected--;
560 
561 	if (fLastSelected != -1 && index < fLastSelected)
562 		fLastSelected--;
563 
564 	_InvalidateFrom(index);
565 	_FixupScrollBar();
566 
567 	return item;
568 }
569 
570 // RemoveItem
571 bool
572 BListView::RemoveItem(BListItem *item)
573 {
574 	return BListView::RemoveItem(IndexOf(item)) != NULL;
575 }
576 
577 // RemoveItems
578 bool
579 BListView::RemoveItems(int32 index, int32 count)
580 {
581 	if (index >= CountItems())
582 		index = -1;
583 
584 	if (index < 0)
585 		return false;
586 
587 	// TODO: very bad for performance!!
588 	while (count--)
589 		BListView::RemoveItem(index);
590 
591 	return true;
592 }
593 
594 // SetSelectionMessage
595 void
596 BListView::SetSelectionMessage(BMessage* message)
597 {
598 	delete fSelectMessage;
599 	fSelectMessage = message;
600 }
601 
602 // SetInvocationMessage
603 void
604 BListView::SetInvocationMessage(BMessage* message)
605 {
606 	BInvoker::SetMessage(message);
607 }
608 
609 // InvocationMessage
610 BMessage*
611 BListView::InvocationMessage() const
612 {
613 	return BInvoker::Message();
614 }
615 
616 // InvocationCommand
617 uint32
618 BListView::InvocationCommand() const
619 {
620 	return BInvoker::Command();
621 }
622 
623 // SelectionMessage
624 BMessage*
625 BListView::SelectionMessage() const
626 {
627 	return fSelectMessage;
628 }
629 
630 // SelectionCommand
631 uint32
632 BListView::SelectionCommand() const
633 {
634 	if (fSelectMessage)
635 		return fSelectMessage->what;
636 	else
637 		return 0;
638 }
639 
640 // SetListType
641 void
642 BListView::SetListType(list_view_type type)
643 {
644 	if (fListType == B_MULTIPLE_SELECTION_LIST &&
645 		type == B_SINGLE_SELECTION_LIST)
646 		Select(CurrentSelection(0));
647 
648 	fListType = type;
649 }
650 //------------------------------------------------------------------------------
651 list_view_type BListView::ListType() const
652 {
653 	return fListType;
654 }
655 
656 // ItemAt
657 BListItem*
658 BListView::ItemAt(int32 index) const
659 {
660 	return (BListItem*)fList.ItemAt(index);
661 }
662 
663 // IndexOf
664 int32
665 BListView::IndexOf(BListItem *item) const
666 {
667 	return fList.IndexOf(item);
668 }
669 
670 // IndexOf
671 int32
672 BListView::IndexOf(BPoint point) const
673 {
674 	float y = 0.0f;
675 
676 	// TODO: somehow binary search, but items don't know their frame
677 	for (int i = 0; i < fList.CountItems(); i++) {
678 		y += ItemAt(i)->Height();
679 
680 		if (point.y < y)
681 			return i;
682 	}
683 
684 	return -1;
685 }
686 
687 // FirstItem
688 BListItem*
689 BListView::FirstItem() const
690 {
691 	return (BListItem*)fList.FirstItem();
692 }
693 
694 // LastItem
695 BListItem*
696 BListView::LastItem() const
697 {
698 	return (BListItem*)fList.LastItem();
699 }
700 
701 // HasItem
702 bool
703 BListView::HasItem(BListItem *item) const
704 {
705 	return fList.HasItem(item);
706 }
707 
708 // CountItems
709 int32
710 BListView::CountItems() const
711 {
712 	return fList.CountItems();
713 }
714 
715 // MakeEmpty
716 void
717 BListView::MakeEmpty()
718 {
719 	_DeselectAll(-1, -1);
720 	fList.MakeEmpty();
721 
722 	Invalidate();
723 }
724 
725 // IsEmpty
726 bool
727 BListView::IsEmpty() const
728 {
729 	return fList.IsEmpty();
730 }
731 
732 // DoForEach
733 void
734 BListView::DoForEach(bool (*func)(BListItem*))
735 {
736 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
737 }
738 
739 // DoForEach
740 void
741 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg )
742 {
743 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
744 }
745 
746 // Items
747 const BListItem**
748 BListView::Items() const
749 {
750 	return (const BListItem**)fList.Items();
751 }
752 
753 // InvalidateItem
754 void
755 BListView::InvalidateItem(int32 index)
756 {
757 	Invalidate(ItemFrame(index));
758 }
759 
760 // ScrollToSelection
761 void
762 BListView::ScrollToSelection()
763 {
764 	BRect itemFrame = ItemFrame(CurrentSelection(0));
765 
766 	if (Bounds().Intersects(itemFrame.InsetByCopy(0.0f, 2.0f)))
767 		return;
768 
769 	if (itemFrame.top < Bounds().top)
770 		ScrollTo(0, itemFrame.top);
771 	else
772 		ScrollTo(0, itemFrame.bottom - Bounds().Height());
773 }
774 
775 
776 void
777 BListView::Select(int32 index, bool extend)
778 {
779 	if (_Select(index, extend)) {
780 		SelectionChanged();
781 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
782 	}
783 }
784 
785 
786 void
787 BListView::Select(int32 start, int32 finish, bool extend)
788 {
789 	if (_Select(start, finish, extend)) {
790 		SelectionChanged();
791 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
792 	}
793 }
794 
795 
796 bool
797 BListView::IsItemSelected(int32 index) const
798 {
799 	BListItem *item = ItemAt(index);
800 	if (item)
801 		return item->IsSelected();
802 
803 	return false;
804 }
805 
806 // CurrentSelection
807 int32
808 BListView::CurrentSelection(int32 index) const
809 {
810 	if (fFirstSelected == -1)
811 		return -1;
812 
813 	if (index == 0)
814 		return fFirstSelected;
815 
816 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
817 		if (ItemAt(i)->IsSelected()) {
818 			if (index == 0)
819 				return i;
820 
821 			index--;
822 		}
823 	}
824 
825 	return -1;
826 }
827 
828 // Invoke
829 status_t
830 BListView::Invoke(BMessage *message)
831 {
832 	// Note, this is more or less a copy of BControl::Invoke() and should
833 	// stay that way (ie. changes done there should be adopted here)
834 
835 	bool notify = false;
836 	uint32 kind = InvokeKind(&notify);
837 
838 	BMessage clone(kind);
839 	status_t err = B_BAD_VALUE;
840 
841 	if (!message && !notify)
842 		message = Message();
843 
844 	if (!message) {
845 		if (!IsWatched())
846 			return err;
847 	} else
848 		clone = *message;
849 
850 	clone.AddInt64("when", (int64)system_time());
851 	clone.AddPointer("source", this);
852 	clone.AddMessenger("be:sender", BMessenger(this));
853 
854 	if (fListType == B_SINGLE_SELECTION_LIST)
855 		clone.AddInt32("index", fFirstSelected);
856 	else {
857 		if (fFirstSelected >= 0) {
858 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
859 				if (ItemAt(i)->IsSelected())
860 					clone.AddInt32("index", i);
861 			}
862 		}
863 	}
864 
865 	if (message)
866 		err = BInvoker::Invoke(&clone);
867 
868 	SendNotices(kind, &clone);
869 
870 	return err;
871 }
872 
873 
874 void
875 BListView::DeselectAll()
876 {
877 	if (_DeselectAll(-1, -1)) {
878 		SelectionChanged();
879 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
880 	}
881 }
882 
883 
884 void
885 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
886 {
887 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
888 		return;
889 
890 	if (_DeselectAll(exceptFrom, exceptTo)) {
891 		SelectionChanged();
892 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
893 	}
894 }
895 
896 
897 void
898 BListView::Deselect(int32 index)
899 {
900 	if (_Deselect(index)) {
901 		SelectionChanged();
902 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
903 	}
904 }
905 
906 
907 void
908 BListView::SelectionChanged()
909 {
910 	// Hook method to be implemented by subclasses
911 }
912 
913 
914 void
915 BListView::SortItems(int (*cmp)(const void *, const void *))
916 {
917 	if (_DeselectAll(-1, -1)) {
918 		SelectionChanged();
919 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
920 	}
921 
922 	fList.SortItems(cmp);
923 	Invalidate();
924 }
925 
926 // SwapItems
927 bool
928 BListView::SwapItems(int32 a, int32 b)
929 {
930 	MiscData data;
931 
932 	data.swap.a = a;
933 	data.swap.b = b;
934 
935 	return DoMiscellaneous(B_SWAP_OP, &data);
936 }
937 
938 // MoveItem
939 bool
940 BListView::MoveItem(int32 from, int32 to)
941 {
942 	MiscData data;
943 
944 	data.move.from = from;
945 	data.move.to = to;
946 
947 	return DoMiscellaneous(B_MOVE_OP, &data);
948 }
949 
950 // ReplaceItem
951 bool
952 BListView::ReplaceItem(int32 index, BListItem *item)
953 {
954 	MiscData data;
955 
956 	data.replace.index = index;
957 	data.replace.item = item;
958 
959 	return DoMiscellaneous(B_REPLACE_OP, &data);
960 }
961 
962 // AttachedToWindow
963 void
964 BListView::AttachedToWindow()
965 {
966 	BView::AttachedToWindow();
967 	_FontChanged();
968 
969 	if (!Messenger().IsValid())
970 		SetTarget(Window(), NULL);
971 
972 	_FixupScrollBar();
973 }
974 
975 // FrameMoved
976 void
977 BListView::FrameMoved(BPoint new_position)
978 {
979 	BView::FrameMoved(new_position);
980 }
981 
982 // ItemFrame
983 BRect
984 BListView::ItemFrame(int32 index)
985 {
986 	BRect frame(0, 0, Bounds().Width(), -1);
987 
988 	if (index < 0 || index >= CountItems())
989 		return frame;
990 
991 	// TODO: this is very expensive, the (last) offsets could be cached
992 	for (int32 i = 0; i <= index; i++) {
993 		frame.top = frame.bottom + 1;
994 		frame.bottom += (float)ceil(ItemAt(i)->Height());
995 	}
996 
997 	return frame;
998 }
999 
1000 
1001 BHandler*
1002 BListView::ResolveSpecifier(BMessage* message, int32 index,
1003 	BMessage* specifier, int32 form, const char* property)
1004 {
1005 	BPropertyInfo propInfo(sProperties);
1006 
1007 	if (propInfo.FindMatch(message, 0, specifier, form, property) < 0)
1008 		return BView::ResolveSpecifier(message, index, specifier, form, property);
1009 
1010 	// TODO: msg->AddInt32("_match_code_", );
1011 
1012 	return this;
1013 }
1014 
1015 
1016 status_t
1017 BListView::GetSupportedSuites(BMessage* data)
1018 {
1019 	BPropertyInfo propertyInfo(sProperties);
1020 
1021 	data->AddString("suites", "suite/vnd.Be-list-view");
1022 	data->AddFlat("messages", &propertyInfo);
1023 
1024 	return BView::GetSupportedSuites(data);
1025 }
1026 
1027 // Perform
1028 status_t
1029 BListView::Perform(perform_code d, void *arg)
1030 {
1031 	return BView::Perform(d, arg);
1032 }
1033 
1034 // WindowActivated
1035 void
1036 BListView::WindowActivated(bool state)
1037 {
1038 	BView::WindowActivated(state);
1039 
1040 	if (IsFocus()) {
1041 		Invalidate();
1042 	}
1043 }
1044 
1045 // DetachedFromWindow
1046 void
1047 BListView::DetachedFromWindow()
1048 {
1049 	BView::DetachedFromWindow();
1050 }
1051 
1052 // InitiateDrag
1053 bool
1054 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected)
1055 {
1056 	return false;
1057 }
1058 
1059 // ResizeToPreferred
1060 void
1061 BListView::ResizeToPreferred()
1062 {
1063 	BView::ResizeToPreferred();
1064 }
1065 
1066 // GetPreferredSize
1067 void
1068 BListView::GetPreferredSize(float *width, float *height)
1069 {
1070 	BView::GetPreferredSize(width, height);
1071 }
1072 
1073 // AllAttached
1074 void
1075 BListView::AllAttached()
1076 {
1077 	BView::AllAttached();
1078 }
1079 
1080 // AllDetached
1081 void
1082 BListView::AllDetached()
1083 {
1084 	BView::AllDetached();
1085 }
1086 
1087 // DoMiscellaneous
1088 bool
1089 BListView::DoMiscellaneous(MiscCode code, MiscData *data)
1090 {
1091 	if (code > B_SWAP_OP)
1092 		return false;
1093 
1094 	switch (code) {
1095 		case B_NO_OP:
1096 			break;
1097 
1098 		case B_REPLACE_OP:
1099 			return _ReplaceItem(data->replace.index, data->replace.item);
1100 
1101 		case B_MOVE_OP:
1102 			return _MoveItem(data->move.from, data->move.to);
1103 
1104 		case B_SWAP_OP:
1105 			return _SwapItems(data->swap.a, data->swap.b);
1106 	}
1107 
1108 	return false;
1109 }
1110 
1111 void BListView::_ReservedListView2() {}
1112 void BListView::_ReservedListView3() {}
1113 void BListView::_ReservedListView4() {}
1114 
1115 BListView &BListView::operator=(const BListView &)
1116 {
1117 	return *this;
1118 }
1119 
1120 // _InitObject
1121 void
1122 BListView::_InitObject(list_view_type type)
1123 {
1124 	fListType = type;
1125 	fFirstSelected = -1;
1126 	fLastSelected = -1;
1127 	fAnchorIndex = -1;
1128 	fWidth = Bounds().Width();
1129 	fSelectMessage = NULL;
1130 	fScrollView = NULL;
1131 	fTrack = new track_data;
1132 	fTrack->try_drag = false;
1133 	fTrack->item_index = -1;
1134 }
1135 
1136 // _FixupScrollBar
1137 void
1138 BListView::_FixupScrollBar()
1139 {
1140 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1141 
1142 	if (!vertScroller)
1143 		return;
1144 
1145 	BRect bounds = Bounds();
1146 	int32 count = CountItems();
1147 
1148 	float itemHeight = 0;
1149 	for (int32 i = 0; BListItem* item = ItemAt(i); i++) {
1150 		itemHeight += item->Height();
1151 	}
1152 
1153 	if (bounds.Height() > itemHeight) {
1154 		// no scrolling
1155 		vertScroller->SetRange(0.0, 0.0);
1156 		vertScroller->SetValue(0.0);
1157 			// also scrolls ListView to the top
1158 	} else {
1159 		vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1160 		vertScroller->SetProportion(bounds.Height () / itemHeight);
1161 		// scroll up if there is empty room on bottom
1162 		if (itemHeight < bounds.bottom) {
1163 			ScrollBy(0.0, bounds.bottom - itemHeight);
1164 		}
1165 	}
1166 
1167 	if (count != 0) {
1168 		vertScroller->SetSteps((float)ceil(FirstItem()->Height()),
1169 			bounds.Height());
1170 	}
1171 }
1172 
1173 // _InvalidateFrom
1174 void
1175 BListView::_InvalidateFrom(int32 index)
1176 {
1177 	// make sure index is behind last valid index
1178 	int32 count = CountItems();
1179 	if (index >= count) {
1180 		index = count;
1181 	}
1182 	// take the item before the wanted one,
1183 	// because that might already be removed
1184 	index--;
1185 	BRect dirty = Bounds();
1186 	if (index >= 0) {
1187 		dirty.top = ItemFrame(index).bottom + 1;
1188 	}
1189 	Invalidate(dirty);
1190 }
1191 
1192 // _FontChanged
1193 void
1194 BListView::_FontChanged()
1195 {
1196 	BFont font;
1197 	GetFont(&font);
1198 
1199 	for (int i = 0; i < CountItems (); i ++)
1200 		ItemAt(i)->Update(this, &font);
1201 }
1202 
1203 
1204 /*!
1205 	Selects the item at the specified \a index, and returns \c true in
1206 	case the selection was changed because of this method.
1207 	If \a extend is \c false, all previously selected items are deselected.
1208 */
1209 bool
1210 BListView::_Select(int32 index, bool extend)
1211 {
1212 	if (index < 0 || index >= CountItems())
1213 		return false;
1214 
1215 	// only lock the window when there is one
1216 	BAutolock locker(Window());
1217 	if (Window() && !locker.IsLocked())
1218 		return false;
1219 
1220 	bool changed = false;
1221 
1222 	if (fFirstSelected != -1 && !extend)
1223 		changed = _DeselectAll(index, index);
1224 
1225 	BListItem* item = ItemAt(index);
1226 	if (!item->IsEnabled() || item->IsSelected()) {
1227 		// if the item is already selected, or can't be selected,
1228 		// we're done here
1229 		return changed;
1230 	}
1231 
1232 	// keep track of first and last selected item
1233 	if (fFirstSelected == -1) {
1234 		// no previous selection
1235 		fFirstSelected = index;
1236 		fLastSelected = index;
1237 	} else if (index < fFirstSelected) {
1238 		fFirstSelected = index;
1239 	} else if (index > fLastSelected) {
1240 		fLastSelected = index;
1241 	}
1242 
1243 	ItemAt(index)->Select();
1244 	if (Window())
1245 		InvalidateItem(index);
1246 
1247 	return true;
1248 }
1249 
1250 
1251 /*!
1252 	Selects the items between \a from and \a to, and returns \c true in
1253 	case the selection was changed because of this method.
1254 	If \a extend is \c false, all previously selected items are deselected.
1255 */
1256 bool
1257 BListView::_Select(int32 from, int32 to, bool extend)
1258 {
1259 	if (to < from)
1260 		return false;
1261 
1262 	BAutolock locker(Window());
1263 	if (Window() && !locker.IsLocked())
1264 		return false;
1265 
1266 	bool changed = false;
1267 
1268 	if (fFirstSelected != -1 && !extend)
1269 		changed = _DeselectAll(from, to);
1270 
1271 	if (fFirstSelected == -1) {
1272 		fFirstSelected = from;
1273 		fLastSelected = to;
1274 	} else if (from < fFirstSelected)
1275 		fFirstSelected = from;
1276 	else if (to > fLastSelected)
1277 		fLastSelected = to;
1278 
1279 	for (int32 i = from; i <= to; ++i) {
1280 		BListItem *item = ItemAt(i);
1281 		if (item && !item->IsSelected()) {
1282 			item->Select();
1283 			if (Window())
1284 				InvalidateItem(i);
1285 			changed = true;
1286 		}
1287 	}
1288 
1289 	return changed;
1290 }
1291 
1292 
1293 bool
1294 BListView::_Deselect(int32 index)
1295 {
1296 	if (index < 0 || index >= CountItems())
1297 		return false;
1298 
1299 	BAutolock locker(Window());
1300 	if (Window() && !locker.IsLocked())
1301 		return false;
1302 
1303 	BListItem *item = ItemAt(index);
1304 
1305 	if (item && item->IsSelected()) {
1306 		BRect frame(ItemFrame(index));
1307 		BRect bounds(Bounds());
1308 
1309 		item->Deselect();
1310 
1311 		if (fFirstSelected == index && fLastSelected == index) {
1312 			fFirstSelected = -1;
1313 			fLastSelected = -1;
1314 		} else {
1315 			if (fFirstSelected == index)
1316 				fFirstSelected = _CalcFirstSelected(index);
1317 
1318 			if (fLastSelected == index)
1319 				fLastSelected = _CalcLastSelected(index);
1320 		}
1321 
1322 		if (bounds.Intersects(frame))
1323 			DrawItem(ItemAt(index), frame, true);
1324 	}
1325 
1326 	return true;
1327 }
1328 
1329 
1330 bool
1331 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1332 {
1333 	if (fFirstSelected == -1)
1334 		return false;
1335 
1336 	BAutolock locker(Window());
1337 	if (Window() && !locker.IsLocked())
1338 		return false;
1339 
1340 	bool changed = false;
1341 
1342 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1343 		// don't deselect the items we shouldn't deselect
1344 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1345 			continue;
1346 
1347 		BListItem *item = ItemAt(index);
1348 		if (item && item->IsSelected()) {
1349 			item->Deselect();
1350 			InvalidateItem(index);
1351 			changed = true;
1352 		}
1353 	}
1354 
1355 	if (!changed)
1356 		return false;
1357 
1358 	if (exceptFrom != -1) {
1359 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1360 		fLastSelected = _CalcLastSelected(exceptTo);
1361 	} else
1362 		fFirstSelected = fLastSelected = -1;
1363 
1364 	return true;
1365 }
1366 
1367 // _TryInitiateDrag
1368 bool
1369 BListView::_TryInitiateDrag(BPoint where)
1370 {
1371 	if (!fTrack->try_drag | fTrack->item_index < 0)
1372 		return false;
1373 
1374 	BPoint offset = where - fTrack->drag_start;
1375 	float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1376 
1377 	if (dragDistance > 5.0) {
1378 		fTrack->try_drag = false;
1379 		return InitiateDrag(fTrack->drag_start, fTrack->item_index, fTrack->was_selected);;
1380 	}
1381 	return false;
1382 }
1383 
1384 // _CalcFirstSelected
1385 int32
1386 BListView::_CalcFirstSelected(int32 after)
1387 {
1388 	if (after >= CountItems())
1389 		return -1;
1390 
1391 	int32 count = CountItems();
1392 	for (int32 i = after; i < count; i++) {
1393 		if (ItemAt(i)->IsSelected())
1394 			return i;
1395 	}
1396 
1397 	return -1;
1398 }
1399 
1400 
1401 int32
1402 BListView::_CalcLastSelected(int32 before)
1403 {
1404 	if (before < 0)
1405 		return -1;
1406 
1407 	before = min_c(CountItems() - 1, before);
1408 
1409 	for (int32 i = before; i >= 0; i--) {
1410 		if (ItemAt(i)->IsSelected())
1411 			return i;
1412 	}
1413 
1414 	return -1;
1415 }
1416 
1417 
1418 void
1419 BListView::DrawItem(BListItem *item, BRect itemRect, bool complete)
1420 {
1421 	item->DrawItem(this, itemRect, complete);
1422 }
1423 
1424 
1425 bool
1426 BListView::_SwapItems(int32 a, int32 b)
1427 {
1428 	// remember frames of items before anyhing happens,
1429 	// the tricky situation is when the two items have
1430 	// a different height
1431 	BRect aFrame = ItemFrame(a);
1432 	BRect bFrame = ItemFrame(b);
1433 
1434 	if (!fList.SwapItems(a, b))
1435 		return false;
1436 
1437 	if (a == b) {
1438 		// nothing to do, but success nevertheless
1439 		return true;
1440 	}
1441 
1442 	// track anchor item
1443 	if (fAnchorIndex == a)
1444 		fAnchorIndex = b;
1445 	else if (fAnchorIndex == b)
1446 		fAnchorIndex = a;
1447 
1448 	// track selection
1449 	// NOTE: this is only important if the selection status
1450 	// of both items is not the same
1451 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1452 		int32 first = min_c(a, b);
1453 		int32 last = max_c(a, b);
1454 		if (first < fFirstSelected || last > fLastSelected) {
1455 			first = min_c(first, fFirstSelected);
1456 			last = max_c(last, fLastSelected);
1457 			_RescanSelection(first, last);
1458 		}
1459 		// though the actually selected items stayed the
1460 		// same, the selection has still changed
1461 		SelectionChanged();
1462 	}
1463 
1464 	// take care of invalidation
1465 	if (Window()) {
1466 		// NOTE: window looper is assumed to be locked!
1467 		if (aFrame.Height() != bFrame.Height()) {
1468 			// items in between shifted visually
1469 			Invalidate(aFrame | bFrame);
1470 		} else {
1471 			Invalidate(aFrame);
1472 			Invalidate(bFrame);
1473 		}
1474 	}
1475 
1476 	return true;
1477 }
1478 
1479 
1480 bool
1481 BListView::_MoveItem(int32 from, int32 to)
1482 {
1483 	// remember item frames before doing anything
1484 	BRect frameFrom = ItemFrame(from);
1485 	BRect frameTo = ItemFrame(to);
1486 
1487 	if (!fList.MoveItem(from, to))
1488 		return false;
1489 
1490 	// track anchor item
1491 	if (fAnchorIndex == from)
1492 		fAnchorIndex = to;
1493 
1494 	// track selection
1495 	if (ItemAt(to)->IsSelected()) {
1496 		_RescanSelection(from, to);
1497 		// though the actually selected items stayed the
1498 		// same, the selection has still changed
1499 		SelectionChanged();
1500 	}
1501 
1502 	// take care of invalidation
1503 	if (Window()) {
1504 		// NOTE: window looper is assumed to be locked!
1505 		Invalidate(frameFrom | frameTo);
1506 	}
1507 
1508 	return true;
1509 }
1510 
1511 
1512 bool
1513 BListView::_ReplaceItem(int32 index, BListItem *item)
1514 {
1515 	if (!item)
1516 		return false;
1517 
1518 	BListItem* old = ItemAt(index);
1519 	if (!old)
1520 		return false;
1521 
1522 	BRect frame = ItemFrame(index);
1523 
1524 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1525 
1526 	// replace item
1527 	if (!fList.ReplaceItem(index, item))
1528 		return false;
1529 
1530 	// tack selection
1531 	if (selectionChanged) {
1532 		int32 start = min_c(fFirstSelected, index);
1533 		int32 end = max_c(fLastSelected, index);
1534 		_RescanSelection(start, end);
1535 		SelectionChanged();
1536 	}
1537 
1538 	bool itemHeightChanged = frame != ItemFrame(index);
1539 
1540 	// take care of invalidation
1541 	if (Window()) {
1542 		// NOTE: window looper is assumed to be locked!
1543 		if (itemHeightChanged)
1544 			_InvalidateFrom(index);
1545 		else
1546 			Invalidate(frame);
1547 	}
1548 
1549 	if (itemHeightChanged)
1550 		_FixupScrollBar();
1551 
1552 	return true;
1553 }
1554 
1555 
1556 void
1557 BListView::_RescanSelection(int32 from, int32 to)
1558 {
1559 	if (from > to) {
1560 		int32 tmp = from;
1561 		from = to;
1562 		to = tmp;
1563 	}
1564 
1565 	from = max_c(0, from);
1566 	to = min_c(to, CountItems() - 1);
1567 
1568 	if (fAnchorIndex != -1) {
1569 		if (fAnchorIndex == from)
1570 			fAnchorIndex = to;
1571 		else if (fAnchorIndex == to)
1572 			fAnchorIndex = from;
1573 	}
1574 
1575 	for (int32 i = from; i <= to; i++) {
1576 		if (ItemAt(i)->IsSelected()) {
1577 			fFirstSelected = i;
1578 			break;
1579 		}
1580 	}
1581 
1582 	if (fFirstSelected > from)
1583 		from = fFirstSelected;
1584 	for (int32 i = from; i <= to; i++) {
1585 		if (ItemAt(i)->IsSelected())
1586 			fLastSelected = i;
1587 	}
1588 }
1589 
1590 
1591