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